From 221d066966195299ee02a1d1120addb481437e41 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 8 Mar 2024 10:56:51 +0100 Subject: [PATCH 01/22] Abstract commonly used functionality from `EntitySource`. --- .../io/source/AssetEntitySource.java | 351 +++++++++++++++++ .../io/source/EnergyManagementSource.java | 10 +- .../ie3/datamodel/io/source/EntitySource.java | 371 ++---------------- .../datamodel/io/source/GraphicSource.java | 10 +- .../io/source/IdCoordinateSource.java | 20 +- .../datamodel/io/source/RawGridSource.java | 14 +- .../io/source/ResultEntitySource.java | 34 +- .../io/source/SystemParticipantSource.java | 22 +- .../datamodel/io/source/ThermalSource.java | 8 +- .../io/source/TimeSeriesMappingSource.java | 8 +- .../datamodel/io/source/TimeSeriesSource.java | 4 +- .../ie3/datamodel/io/source/TypeSource.java | 29 +- .../datamodel/io/source/WeatherSource.java | 12 +- .../couchbase/CouchbaseWeatherSource.java | 19 +- .../io/source/csv/CsvIdCoordinateSource.java | 12 +- .../csv/CsvTimeSeriesMappingSource.java | 17 +- .../io/source/csv/CsvTimeSeriesSource.java | 19 +- .../io/source/csv/CsvWeatherSource.java | 23 +- .../influxdb/InfluxDbWeatherSource.java | 18 +- .../io/source/sql/SqlIdCoordinateSource.java | 29 +- .../sql/SqlTimeSeriesMappingSource.java | 16 +- .../io/source/sql/SqlTimeSeriesSource.java | 38 +- .../io/source/sql/SqlWeatherSource.java | 24 +- ...st.groovy => AssetEntitySourceTest.groovy} | 4 +- .../io/source/IdCoordinateSourceMock.groovy | 7 +- .../ie3/test/common/WeatherTestData.groovy | 7 +- 26 files changed, 578 insertions(+), 548 deletions(-) create mode 100644 src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java rename src/test/groovy/edu/ie3/datamodel/io/source/{EntitySourceTest.groovy => AssetEntitySourceTest.groovy} (98%) diff --git a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java new file mode 100644 index 000000000..72ecf628b --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java @@ -0,0 +1,351 @@ +/* + * © 2023. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.io.source; + +import edu.ie3.datamodel.exceptions.FactoryException; +import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.io.factory.EntityData; +import edu.ie3.datamodel.io.factory.EntityFactory; +import edu.ie3.datamodel.io.factory.input.AssetInputEntityData; +import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData; +import edu.ie3.datamodel.models.Entity; +import edu.ie3.datamodel.models.UniqueEntity; +import edu.ie3.datamodel.models.input.AssetInput; +import edu.ie3.datamodel.models.input.NodeInput; +import edu.ie3.datamodel.models.input.OperatorInput; +import edu.ie3.datamodel.utils.TriFunction; +import edu.ie3.datamodel.utils.Try; +import edu.ie3.datamodel.utils.Try.Failure; +import edu.ie3.datamodel.utils.Try.Success; +import java.util.Map; +import java.util.Optional; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.stream.Stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** Class that provides all functionalities to build entities */ +public abstract class AssetEntitySource extends EntitySource { + + protected static final Logger log = LoggerFactory.getLogger(AssetEntitySource.class); + + protected final DataSource dataSource; + + // field names + protected static final String OPERATOR = "operator"; + protected static final String NODE = "node"; + protected static final String TYPE = "type"; + + protected AssetEntitySource(DataSource dataSource) { + this.dataSource = dataSource; + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + + /** + * Enhances given entity data with an entity from the given entity map. The linked entity is + * chosen by taking into account the UUID found by retrieving the field with given fieldName from + * entityData. + * + * @param entityData The entity data to be enhanced, which also provides a link to another entity + * via UUID + * @param fieldName The field name of the field that provides the UUID of the linked entity + * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity + * data + * @param createEntityData The function that creates the resulting entity data given entityData + * and the linked entity + * @param Type of input entity data + * @param Type of the linked entity + * @param Type of resulting entity data that combines the given entityData and linked entity + * @return {@link Try} to enhanced data + */ + protected static + Try enrichEntityData( + E entityData, + String fieldName, + Map linkedEntities, + BiFunction createEntityData) { + return getLinkedEntity(entityData, fieldName, linkedEntities) + .map( + linkedEntity -> { + Map fieldsToAttributes = entityData.getFieldsToValues(); + + // remove fields that are passed as objects to constructor + fieldsToAttributes.keySet().remove(fieldName); + + // build resulting entity data + return createEntityData.apply(entityData, linkedEntity); + }); + } + + /** + * Enhances given entity data with two entities from the given entity maps. The linked entities + * are chosen by taking into account the UUIDs found by retrieving the fields with given + * fieldName1 and fieldName2 from entityData. + * + * @param entityData The entity data to be enhanced, which also provides links to two other + * entities via UUID + * @param fieldName1 The field name of the field that provides the UUID of the first linked entity + * @param linkedEntities1 The first map of UUID to entities, of which one should be linked to + * given entity data + * @param fieldName2 The field name of the field that provides the UUID of the second linked + * entity + * @param linkedEntities2 The second map of UUID to entities, of which one should be linked to + * given entity data + * @param createEntityData The function that creates the resulting entity data given entityData + * and the linked entities + * @param Type of input entity data + * @param Type of the first linked entity + * @param Type of the second linked entity + * @param Type of resulting entity data that combines the given entityData and two linked + * entities + * @return {@link Try} to enhanced data + */ + protected static < + E extends EntityData, T1 extends UniqueEntity, T2 extends UniqueEntity, R extends E> + Try enrichEntityData( + E entityData, + String fieldName1, + Map linkedEntities1, + String fieldName2, + Map linkedEntities2, + TriFunction createEntityData) { + return getLinkedEntity(entityData, fieldName1, linkedEntities1) + .flatMap( + linkedEntity1 -> + getLinkedEntity(entityData, fieldName2, linkedEntities2) + .map( + linkedEntity2 -> { + Map fieldsToAttributes = entityData.getFieldsToValues(); + + // remove fields that are passed as objects to constructor + fieldsToAttributes.keySet().remove(fieldName1); + fieldsToAttributes.keySet().remove(fieldName2); + + // build resulting entity data + return createEntityData.apply(entityData, linkedEntity1, linkedEntity2); + })); + } + + /** + * Checks if the linked entity can be found in the provided map of entities. The linked entities + * are chosen by taking into account the UUIDs found by retrieving the fields with given + * fieldName1 and fieldName2 from entityData. + * + * @param entityData The entity data of the entity that provides a link to another entity via UUID + * @param fieldName The field name of the field that provides the UUID of the linked entity + * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity + * data + * @param the type of the resulting linked entity instance + * @return a {@link Success} containing the entity or a {@link Failure} if the entity cannot be + * found + */ + protected static Try getLinkedEntity( + EntityData entityData, String fieldName, Map linkedEntities) { + + return Try.of(() -> entityData.getUUID(fieldName), FactoryException.class) + .transformF( + exception -> + new SourceException( + "Extracting UUID field " + + fieldName + + " from entity data " + + entityData.toString() + + " failed.", + exception)) + .flatMap( + entityUuid -> + getEntity(entityUuid, linkedEntities) + .transformF( + exception -> + new SourceException( + "Linked " + + fieldName + + " with UUID " + + entityUuid + + " was not found for entity " + + entityData, + exception))); + } + + /** + * Enhances given entity data with an entity from the given entity map or the default value. The + * linked entity is possibly chosen by taking into account the UUID found by retrieving the field + * with given fieldName from entityData. If no entity is linked, the default value is used. + * + * @param entityData The entity data to be enhanced, which also might provide a link to another + * entity via UUID + * @param fieldName The field name of the field that might provide the UUID of the linked entity + * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity + * data + * @param defaultEntity The default linked entity to use, if no actual linked entity could be + * found + * @param createEntityData The function that creates the resulting entity data given entityData + * and the linked entity (either retrieved from the map or the standard entity) + * @param Type of input entity data + * @param Type of the linked entity + * @param Type of resulting entity data that combines the given entityData and linked entity + * @return {@link Try} to enhanced data + */ + protected static + Try optionallyEnrichEntityData( + E entityData, + String fieldName, + Map linkedEntities, + T defaultEntity, + BiFunction createEntityData) { + return entityData + .getFieldOptional(fieldName) + .filter(s -> !s.isBlank()) + .map( + // Entity data includes a non-empty UUID String for the desired entity + uuidString -> + Try.of(() -> UUID.fromString(uuidString), IllegalArgumentException.class) + .transformF( + iae -> + // Parsing error still results in a failure, ... + new SourceException( + String.format( + "Exception while trying to parse UUID of field \"%s\" with value \"%s\"", + fieldName, uuidString), + iae)) + .flatMap( + entityUuid -> + getEntity(entityUuid, linkedEntities) + // ... as well as a provided entity UUID that does not match any + // given data + .transformF( + exception -> + new SourceException( + "Linked " + + fieldName + + " with UUID " + + entityUuid + + " was not found for entity " + + entityData, + exception)))) + .orElseGet( + () -> { + // No UUID was given (column does not exist, or field is empty). + // This is totally fine - we successfully return the default value + log.debug( + "Input source for class {} is missing the '{}' field. " + + "Default value '{}' is used.", + entityData.getTargetClass().getSimpleName(), + fieldName, + defaultEntity); + return new Try.Success<>(defaultEntity); + }) + .map( + linkedEntity -> { + Map fieldsToAttributes = entityData.getFieldsToValues(); + + // remove fields that are passed as objects to constructor + fieldsToAttributes.keySet().remove(fieldName); + + // build resulting entity data + return createEntityData.apply(entityData, linkedEntity); + }); + } + + private static Try getEntity(UUID uuid, Map entityMap) { + return Optional.ofNullable(entityMap.get(uuid)) + // We either find a matching entity for given UUID, thus return a success + .map(entity -> Try.of(() -> entity, SourceException.class)) + // ... or find no matching entity, returning a failure. + .orElse( + new Try.Failure<>( + new SourceException("Entity with uuid " + uuid + " was not provided."))); + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- + + /** + * Returns a stream of {@link Try} entities that can be built by using {@link + * NodeAssetInputEntityData} and their corresponding factory. + * + * @param entityClass the entity class that should be build + * @param nodes a map of UUID to {@link NodeInput} entities that should be used to build the + * entities + * @param operators a map of UUID to {@link OperatorInput} entities should be used to build the + * entities + * @return stream of tries of the entities that has been built by the factory + */ + protected Stream> buildNodeAssetEntityData( + Class entityClass, + Map operators, + Map nodes) { + return nodeAssetInputEntityDataStream(buildAssetInputEntityData(entityClass, operators), nodes); + } + + /** + * Returns a stream of tries of {@link NodeAssetInputEntityData} that can be used to build + * instances of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} + * that consumes this data. + * + * @param assetInputEntityDataStream a stream consisting of {@link AssetInputEntityData} that is + * enriched with {@link NodeInput} data + * @param nodes a map of UUID to {@link NodeInput} entities that should be used to build the data + * @return stream of the entity data wrapped in a {@link Try} + */ + protected static Stream> + nodeAssetInputEntityDataStream( + Stream> assetInputEntityDataStream, + Map nodes) { + return assetInputEntityDataStream + .parallel() + .map( + assetInputEntityDataTry -> + assetInputEntityDataTry.flatMap( + assetInputEntityData -> + enrichEntityData( + assetInputEntityData, NODE, nodes, NodeAssetInputEntityData::new))); + } + + /** + * Returns a stream of optional {@link AssetInputEntityData} that can be used to build instances + * of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} that + * consumes this data. + * + * @param entityClass the entity class that should be build + * @param operators a map of UUID to {@link OperatorInput} entities that should be used to build + * the data + * @return stream of the entity data wrapped in a {@link Try} + */ + protected Stream> buildAssetInputEntityData( + Class entityClass, Map operators) { + return assetInputEntityDataStream(buildEntityData(entityClass, dataSource), operators); + } + + /** + * Returns a stream of tries of {@link AssetInputEntityData} that can be used to build instances + * of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} that + * consumes this data. + * + * @param entityDataStream a stream consisting of {@link EntityData} that is enriched with {@link + * OperatorInput} data + * @param operators map of UUID to {@link OperatorInput} entities that should be used to build the + * data + * @return stream of the entity data wrapped in a {@link Try} + */ + protected static Stream> assetInputEntityDataStream( + Stream> entityDataStream, + Map operators) { + return entityDataStream + .parallel() + .map( + entityDataTry -> + entityDataTry.flatMap( + entityData -> + optionallyEnrichEntityData( + entityData, + OPERATOR, + operators, + OperatorInput.NO_OPERATOR_ASSIGNED, + AssetInputEntityData::new))); + } +} diff --git a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java index acca23634..9172aba09 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java @@ -21,7 +21,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -public class EnergyManagementSource extends EntitySource { +public class EnergyManagementSource extends AssetEntitySource { private final TypeSource typeSource; @@ -34,7 +34,7 @@ public EnergyManagementSource(TypeSource typeSource, DataSource dataSource) { @Override public void validate() throws ValidationException { - validate(EmInput.class, emInputFactory).getOrThrow(); + validate(EmInput.class, dataSource, emInputFactory).getOrThrow(); } /** @@ -73,9 +73,9 @@ public Map getEmUnits(Map operators) throws /** * Since each EM can itself be controlled by another EM, it does not suffice to link {@link - * EmInput}s via {@link EntitySource#optionallyEnrichEntityData} as we do for system participants - * in {@link SystemParticipantSource}. Instead, we use a recursive approach, starting with EMs at - * root level (which are not EM-controlled themselves). + * EmInput}s via {@link AssetEntitySource#optionallyEnrichEntityData} as we do for system + * participants in {@link SystemParticipantSource}. Instead, we use a recursive approach, starting + * with EMs at root level (which are not EM-controlled themselves). * * @param assetEntityDataStream the data stream of {@link AssetInputEntityData} {@link Try} * objects diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index ff5e5a6b3..83f09e0a0 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -1,5 +1,5 @@ /* - * © 2023. TU Dortmund University, + * © 2024. TU Dortmund University, * Institute of Energy Systems, Energy Efficiency and Energy Economics, * Research group Distribution grid planning and operation */ @@ -11,58 +11,58 @@ import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.EntityFactory; -import edu.ie3.datamodel.io.factory.input.AssetInputEntityData; -import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData; import edu.ie3.datamodel.models.Entity; import edu.ie3.datamodel.models.UniqueEntity; -import edu.ie3.datamodel.models.input.AssetInput; -import edu.ie3.datamodel.models.input.NodeInput; -import edu.ie3.datamodel.models.input.OperatorInput; -import edu.ie3.datamodel.utils.TriFunction; import edu.ie3.datamodel.utils.Try; -import edu.ie3.datamodel.utils.Try.Failure; -import edu.ie3.datamodel.utils.Try.Success; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; -import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -/** Class that provides all functionalities to build entities */ +/** + * Base class for all entity sources. This class provides some functionalities that are common among + * sources. + */ public abstract class EntitySource { - protected static final Logger log = LoggerFactory.getLogger(EntitySource.class); - - // field names - protected static final String OPERATOR = "operator"; - protected static final String NODE = "node"; - protected static final String TYPE = "type"; - - protected final DataSource dataSource; - - protected EntitySource(DataSource dataSource) { - this.dataSource = dataSource; - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + protected EntitySource() {} + /** + * Method for validating a given {@link EntitySource}. + * + * @throws ValidationException - if an error occurred while validating the source + */ public abstract void validate() throws ValidationException; /** * Method for validating a single source. * * @param entityClass class to be validated + * @param dataSource source for the fields * @param validator used to validate * @param type of the class */ protected final Try validate( - Class entityClass, SourceValidator validator) { - return Try.of(() -> dataSource.getSourceFields(entityClass), SourceException.class) + Class entityClass, DataSource dataSource, SourceValidator validator) { + return validate(entityClass, () -> dataSource.getSourceFields(entityClass), validator); + } + + /** + * Method for validating a single source. + * + * @param entityClass class to be validated + * @param sourceFields supplier for source fields + * @param validator used to validate + * @param type of the class + */ + protected final Try validate( + Class entityClass, + Try.TrySupplier>, SourceException> sourceFields, + SourceValidator validator) { + return Try.of(sourceFields, SourceException.class) .transformF( se -> (ValidationException) @@ -78,326 +78,23 @@ protected final Try validate( .orElse(Try.Success.empty())); } - /** - * Enhances given entity data with an entity from the given entity map. The linked entity is - * chosen by taking into account the UUID found by retrieving the field with given fieldName from - * entityData. - * - * @param entityData The entity data to be enhanced, which also provides a link to another entity - * via UUID - * @param fieldName The field name of the field that provides the UUID of the linked entity - * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity - * data - * @param createEntityData The function that creates the resulting entity data given entityData - * and the linked entity - * @param Type of input entity data - * @param Type of the linked entity - * @param Type of resulting entity data that combines the given entityData and linked entity - * @return {@link Try} to enhanced data - */ - protected static - Try enrichEntityData( - E entityData, - String fieldName, - Map linkedEntities, - BiFunction createEntityData) { - return getLinkedEntity(entityData, fieldName, linkedEntities) - .map( - linkedEntity -> { - Map fieldsToAttributes = entityData.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName); - - // build resulting entity data - return createEntityData.apply(entityData, linkedEntity); - }); - } - - /** - * Enhances given entity data with two entities from the given entity maps. The linked entities - * are chosen by taking into account the UUIDs found by retrieving the fields with given - * fieldName1 and fieldName2 from entityData. - * - * @param entityData The entity data to be enhanced, which also provides links to two other - * entities via UUID - * @param fieldName1 The field name of the field that provides the UUID of the first linked entity - * @param linkedEntities1 The first map of UUID to entities, of which one should be linked to - * given entity data - * @param fieldName2 The field name of the field that provides the UUID of the second linked - * entity - * @param linkedEntities2 The second map of UUID to entities, of which one should be linked to - * given entity data - * @param createEntityData The function that creates the resulting entity data given entityData - * and the linked entities - * @param Type of input entity data - * @param Type of the first linked entity - * @param Type of the second linked entity - * @param Type of resulting entity data that combines the given entityData and two linked - * entities - * @return {@link Try} to enhanced data - */ - protected static < - E extends EntityData, T1 extends UniqueEntity, T2 extends UniqueEntity, R extends E> - Try enrichEntityData( - E entityData, - String fieldName1, - Map linkedEntities1, - String fieldName2, - Map linkedEntities2, - TriFunction createEntityData) { - return getLinkedEntity(entityData, fieldName1, linkedEntities1) - .flatMap( - linkedEntity1 -> - getLinkedEntity(entityData, fieldName2, linkedEntities2) - .map( - linkedEntity2 -> { - Map fieldsToAttributes = entityData.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName1); - fieldsToAttributes.keySet().remove(fieldName2); - - // build resulting entity data - return createEntityData.apply(entityData, linkedEntity1, linkedEntity2); - })); - } - - /** - * Checks if the linked entity can be found in the provided map of entities. The linked entities - * are chosen by taking into account the UUIDs found by retrieving the fields with given - * fieldName1 and fieldName2 from entityData. - * - * @param entityData The entity data of the entity that provides a link to another entity via UUID - * @param fieldName The field name of the field that provides the UUID of the linked entity - * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity - * data - * @param the type of the resulting linked entity instance - * @return a {@link Success} containing the entity or a {@link Failure} if the entity cannot be - * found - */ - protected static Try getLinkedEntity( - EntityData entityData, String fieldName, Map linkedEntities) { - - return Try.of(() -> entityData.getUUID(fieldName), FactoryException.class) - .transformF( - exception -> - new SourceException( - "Extracting UUID field " - + fieldName - + " from entity data " - + entityData.toString() - + " failed.", - exception)) - .flatMap( - entityUuid -> - getEntity(entityUuid, linkedEntities) - .transformF( - exception -> - new SourceException( - "Linked " - + fieldName - + " with UUID " - + entityUuid - + " was not found for entity " - + entityData, - exception))); - } - - /** - * Enhances given entity data with an entity from the given entity map or the default value. The - * linked entity is possibly chosen by taking into account the UUID found by retrieving the field - * with given fieldName from entityData. If no entity is linked, the default value is used. - * - * @param entityData The entity data to be enhanced, which also might provide a link to another - * entity via UUID - * @param fieldName The field name of the field that might provide the UUID of the linked entity - * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity - * data - * @param defaultEntity The default linked entity to use, if no actual linked entity could be - * found - * @param createEntityData The function that creates the resulting entity data given entityData - * and the linked entity (either retrieved from the map or the standard entity) - * @param Type of input entity data - * @param Type of the linked entity - * @param Type of resulting entity data that combines the given entityData and linked entity - * @return {@link Try} to enhanced data - */ - protected static - Try optionallyEnrichEntityData( - E entityData, - String fieldName, - Map linkedEntities, - T defaultEntity, - BiFunction createEntityData) { - return entityData - .getFieldOptional(fieldName) - .filter(s -> !s.isBlank()) - .map( - // Entity data includes a non-empty UUID String for the desired entity - uuidString -> - Try.of(() -> UUID.fromString(uuidString), IllegalArgumentException.class) - .transformF( - iae -> - // Parsing error still results in a failure, ... - new SourceException( - String.format( - "Exception while trying to parse UUID of field \"%s\" with value \"%s\"", - fieldName, uuidString), - iae)) - .flatMap( - entityUuid -> - getEntity(entityUuid, linkedEntities) - // ... as well as a provided entity UUID that does not match any - // given data - .transformF( - exception -> - new SourceException( - "Linked " - + fieldName - + " with UUID " - + entityUuid - + " was not found for entity " - + entityData, - exception)))) - .orElseGet( - () -> { - // No UUID was given (column does not exist, or field is empty). - // This is totally fine - we successfully return the default value - log.debug( - "Input source for class {} is missing the '{}' field. " - + "Default value '{}' is used.", - entityData.getTargetClass().getSimpleName(), - fieldName, - defaultEntity); - return new Try.Success<>(defaultEntity); - }) - .map( - linkedEntity -> { - Map fieldsToAttributes = entityData.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName); - - // build resulting entity data - return createEntityData.apply(entityData, linkedEntity); - }); - } - - private static Try getEntity(UUID uuid, Map entityMap) { - return Optional.ofNullable(entityMap.get(uuid)) - // We either find a matching entity for given UUID, thus return a success - .map(entity -> Try.of(() -> entity, SourceException.class)) - // ... or find no matching entity, returning a failure. - .orElse( - new Try.Failure<>( - new SourceException("Entity with uuid " + uuid + " was not provided."))); - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - /** - * Returns a stream of {@link Try} entities that can be built by using {@link - * NodeAssetInputEntityData} and their corresponding factory. - * - * @param entityClass the entity class that should be build - * @param nodes a map of UUID to {@link NodeInput} entities that should be used to build the - * entities - * @param operators a map of UUID to {@link OperatorInput} entities should be used to build the - * entities - * @return stream of tries of the entities that has been built by the factory - */ - protected Stream> buildNodeAssetEntityData( - Class entityClass, - Map operators, - Map nodes) { - return nodeAssetInputEntityDataStream(buildAssetInputEntityData(entityClass, operators), nodes); - } - - /** - * Returns a stream of tries of {@link NodeAssetInputEntityData} that can be used to build - * instances of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} - * that consumes this data. - * - * @param assetInputEntityDataStream a stream consisting of {@link AssetInputEntityData} that is - * enriched with {@link NodeInput} data - * @param nodes a map of UUID to {@link NodeInput} entities that should be used to build the data - * @return stream of the entity data wrapped in a {@link Try} - */ - protected static Stream> - nodeAssetInputEntityDataStream( - Stream> assetInputEntityDataStream, - Map nodes) { - return assetInputEntityDataStream - .parallel() - .map( - assetInputEntityDataTry -> - assetInputEntityDataTry.flatMap( - assetInputEntityData -> - enrichEntityData( - assetInputEntityData, NODE, nodes, NodeAssetInputEntityData::new))); - } - - /** - * Returns a stream of optional {@link AssetInputEntityData} that can be used to build instances - * of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} that - * consumes this data. - * - * @param entityClass the entity class that should be build - * @param operators a map of UUID to {@link OperatorInput} entities that should be used to build - * the data - * @return stream of the entity data wrapped in a {@link Try} - */ - protected Stream> buildAssetInputEntityData( - Class entityClass, Map operators) { - return assetInputEntityDataStream(buildEntityData(entityClass), operators); - } - - /** - * Returns a stream of tries of {@link AssetInputEntityData} that can be used to build instances - * of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} that - * consumes this data. - * - * @param entityDataStream a stream consisting of {@link EntityData} that is enriched with {@link - * OperatorInput} data - * @param operators map of UUID to {@link OperatorInput} entities that should be used to build the - * data - * @return stream of the entity data wrapped in a {@link Try} - */ - protected static Stream> assetInputEntityDataStream( - Stream> entityDataStream, - Map operators) { - return entityDataStream - .parallel() - .map( - entityDataTry -> - entityDataTry.flatMap( - entityData -> - optionallyEnrichEntityData( - entityData, - OPERATOR, - operators, - OperatorInput.NO_OPERATOR_ASSIGNED, - AssetInputEntityData::new))); - } - /** * Returns a stream of optional {@link EntityData} that can be used to build instances of several * subtypes of {@link Entity} by a corresponding {@link EntityFactory} that consumes this data. * * @param entityClass the entity class that should be build - * @return stream of the entity data wrapped in a {@link Try} + * @param dataSource source for the data + * @return a stream of the entity data wrapped in a {@link Try} */ protected Stream> buildEntityData( - Class entityClass) { - + Class entityClass, DataSource dataSource) { return Try.of(() -> dataSource.getSourceData(entityClass), SourceException.class) .convert( data -> data.map( fieldsToAttributes -> - new Success<>(new EntityData(fieldsToAttributes, entityClass))), - exception -> Stream.of(Failure.of(exception))); + new Try.Success<>(new EntityData(fieldsToAttributes, entityClass))), + exception -> Stream.of(Try.Failure.of(exception))); } protected static Map unpackMap( diff --git a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java index e8a0cf7b0..f1c82fddc 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java @@ -33,7 +33,7 @@ * @version 0.1 * @since 08.04.20 */ -public class GraphicSource extends EntitySource { +public class GraphicSource extends AssetEntitySource { // general fields private final TypeSource typeSource; private final RawGridSource rawGridSource; @@ -55,8 +55,8 @@ public GraphicSource(TypeSource typeSource, RawGridSource rawGridSource, DataSou public void validate() throws ValidationException { Try.scanStream( Stream.of( - validate(NodeGraphicInput.class, nodeGraphicInputFactory), - validate(LineGraphicInput.class, lineGraphicInputFactory)), + validate(NodeGraphicInput.class, dataSource, nodeGraphicInputFactory), + validate(LineGraphicInput.class, dataSource, lineGraphicInputFactory)), "Validation") .transformF(FailedValidationException::new) .getOrThrow(); @@ -162,7 +162,7 @@ public Set getLineGraphicInput(Map lines) */ protected Stream> buildNodeGraphicEntityData( Map nodes) { - return buildEntityData(NodeGraphicInput.class) + return buildEntityData(NodeGraphicInput.class, dataSource) .map( entityDataTry -> entityDataTry.flatMap( @@ -188,7 +188,7 @@ protected Stream> buildNodeGrap */ protected Stream> buildLineGraphicEntityData( Map lines) { - return buildEntityData(LineGraphicInput.class) + return buildEntityData(LineGraphicInput.class, dataSource) .map( entityDataTry -> entityDataTry.flatMap( diff --git a/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java index 33e0daf58..d063f72e8 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java @@ -18,14 +18,14 @@ * latitude and longitude values, which is especially needed for data source that don't offer * combined primary or foreign keys. */ -public interface IdCoordinateSource { +public abstract class IdCoordinateSource extends EntitySource { /** * Method to retrieve the fields found in the source. * * @return an option for the found fields */ - Optional> getSourceFields() throws SourceException; + public abstract Optional> getSourceFields() throws SourceException; /** * Get the matching coordinate for the given ID @@ -33,7 +33,7 @@ public interface IdCoordinateSource { * @param id the ID to look up * @return matching coordinate */ - Optional getCoordinate(int id); + public abstract Optional getCoordinate(int id); /** * Get the matching coordinates for the given IDs @@ -41,7 +41,7 @@ public interface IdCoordinateSource { * @param ids the IDs to look up * @return the matching coordinates */ - Collection getCoordinates(int... ids); + public abstract Collection getCoordinates(int... ids); /** * Get the ID for the coordinate point @@ -49,14 +49,14 @@ public interface IdCoordinateSource { * @param coordinate the coordinate to look up * @return the matching ID */ - Optional getId(Point coordinate); + public abstract Optional getId(Point coordinate); /** * Returns all the coordinates of this source * * @return all available coordinates */ - Collection getAllCoordinates(); + public abstract Collection getAllCoordinates(); /** * Returns the nearest n coordinate points. If n is greater than four, this method will try to @@ -66,7 +66,7 @@ public interface IdCoordinateSource { * @param n number of searched points * @return the nearest n coordinates or all coordinates if n is less than all available points */ - List getNearestCoordinates(Point coordinate, int n); + public abstract List getNearestCoordinates(Point coordinate, int n); /** * Returns the closest n coordinate points to the given coordinate, that are inside a given @@ -79,7 +79,7 @@ public interface IdCoordinateSource { * @param distance to the borders of the envelope that contains the coordinates * @return the nearest n coordinates to the given point */ - List getClosestCoordinates( + public abstract List getClosestCoordinates( Point coordinate, int n, ComparableQuantity distance); /** @@ -92,7 +92,7 @@ List getClosestCoordinates( * @param coordinates the collection of points * @return a list of the nearest n coordinates to the given point or an empty list */ - default List calculateCoordinateDistances( + public List calculateCoordinateDistances( Point coordinate, int n, Collection coordinates) { if (coordinates != null && !coordinates.isEmpty()) { List sortedDistances = @@ -119,7 +119,7 @@ default List calculateCoordinateDistances( * @param numberOfPoints that should be returned * @return list of distances */ - default List restrictToBoundingBox( + public List restrictToBoundingBox( Point coordinate, Collection distances, int numberOfPoints) { boolean topLeft = false; boolean topRight = false; diff --git a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java index 741e821a8..4277ff41d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java @@ -26,7 +26,7 @@ * @version 0.1 * @since 08.04.20 */ -public class RawGridSource extends EntitySource { +public class RawGridSource extends AssetEntitySource { // field names protected static final String NODE_A = "nodeA"; @@ -61,12 +61,12 @@ public RawGridSource(TypeSource typeSource, DataSource dataSource) { public void validate() throws ValidationException { Try.scanStream( Stream.of( - validate(NodeInput.class, nodeInputFactory), - validate(LineInput.class, lineInputFactory), - validate(Transformer2WInput.class, transformer2WInputFactory), - validate(Transformer3WInput.class, transformer3WInputFactory), - validate(SwitchInput.class, switchInputFactory), - validate(MeasurementUnitInput.class, measurementUnitInputFactory)), + validate(NodeInput.class, dataSource, nodeInputFactory), + validate(LineInput.class, dataSource, lineInputFactory), + validate(Transformer2WInput.class, dataSource, transformer2WInputFactory), + validate(Transformer3WInput.class, dataSource, transformer3WInputFactory), + validate(SwitchInput.class, dataSource, switchInputFactory), + validate(MeasurementUnitInput.class, dataSource, measurementUnitInputFactory)), "Validation") .transformF(FailedValidationException::new) .getOrThrow(); diff --git a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java index b5fbd6baa..6aa36588c 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java @@ -43,8 +43,10 @@ public class ResultEntitySource extends EntitySource { private final ConnectorResultFactory connectorResultFactory; private final FlexOptionsResultFactory flexOptionsResultFactory; + private final DataSource dataSource; + public ResultEntitySource(DataSource dataSource) { - super(dataSource); + this.dataSource = dataSource; // init factories this.systemParticipantResultFactory = new SystemParticipantResultFactory(); @@ -56,7 +58,7 @@ public ResultEntitySource(DataSource dataSource) { } public ResultEntitySource(DataSource dataSource, DateTimeFormatter dateTimeFormatter) { - super(dataSource); + this.dataSource = dataSource; // init factories this.systemParticipantResultFactory = new SystemParticipantResultFactory(dateTimeFormatter); @@ -83,19 +85,19 @@ public void validate() throws ValidationException { EvResult.class, HpResult.class, EmResult.class) - .map(c -> validate(c, systemParticipantResultFactory)) + .map(c -> validate(c, dataSource, systemParticipantResultFactory)) .toList()); participantResults.addAll( List.of( - validate(ThermalHouseResult.class, thermalResultFactory), - validate(CylindricalStorageResult.class, thermalResultFactory), - validate(SwitchResult.class, switchResultFactory), - validate(NodeResult.class, nodeResultFactory), - validate(LineResult.class, connectorResultFactory), - validate(Transformer2WResult.class, connectorResultFactory), - validate(Transformer3WResult.class, connectorResultFactory), - validate(FlexOptionsResult.class, flexOptionsResultFactory))); + validate(ThermalHouseResult.class, dataSource, thermalResultFactory), + validate(CylindricalStorageResult.class, dataSource, thermalResultFactory), + validate(SwitchResult.class, dataSource, switchResultFactory), + validate(NodeResult.class, dataSource, nodeResultFactory), + validate(LineResult.class, dataSource, connectorResultFactory), + validate(Transformer2WResult.class, dataSource, connectorResultFactory), + validate(Transformer3WResult.class, dataSource, connectorResultFactory), + validate(FlexOptionsResult.class, dataSource, flexOptionsResultFactory))); Try.scanCollection(participantResults, Void.class) .transformF(FailedValidationException::new) @@ -363,17 +365,17 @@ public Set getEmResults() throws SourceException { * Build and cast entities to the correct type, since result factories outputs result entities of * some general type. * - * @param entityClass - * @param factory - * @return - * @param + * @param entityClass that should be build + * @param factory for building the entity + * @return a set of entities + * @param type of entity */ @SuppressWarnings("unchecked") private Set getResultEntities( Class entityClass, EntityFactory factory) throws SourceException { return unpackSet( - buildEntityData(entityClass) + buildEntityData(entityClass, dataSource) .map(entityData -> factory.get(entityData).map(data -> (T) data)), entityClass); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index 380e79fdb..1e2294cf5 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -30,7 +30,7 @@ * Implementation that provides the capability to build entities of type {@link * SystemParticipantInput} as well as {@link SystemParticipants} container. */ -public class SystemParticipantSource extends EntitySource { +public class SystemParticipantSource extends AssetEntitySource { private static final String THERMAL_STORAGE = "thermalstorage"; private static final String THERMAL_BUS = "thermalbus"; @@ -83,16 +83,16 @@ public SystemParticipantSource( public void validate() throws ValidationException { Try.scanStream( Stream.of( - validate(BmInput.class, bmInputFactory), - validate(ChpInput.class, chpInputFactory), - validate(EvInput.class, evInputFactory), - validate(FixedFeedInInput.class, fixedFeedInInputFactory), - validate(HpInput.class, hpInputFactory), - validate(LoadInput.class, loadInputFactory), - validate(PvInput.class, pvInputFactory), - validate(StorageInput.class, storageInputFactory), - validate(WecInput.class, wecInputFactory), - validate(EvcsInput.class, evcsInputFactory)), + validate(BmInput.class, dataSource, bmInputFactory), + validate(ChpInput.class, dataSource, chpInputFactory), + validate(EvInput.class, dataSource, evInputFactory), + validate(FixedFeedInInput.class, dataSource, fixedFeedInInputFactory), + validate(HpInput.class, dataSource, hpInputFactory), + validate(LoadInput.class, dataSource, loadInputFactory), + validate(PvInput.class, dataSource, pvInputFactory), + validate(StorageInput.class, dataSource, storageInputFactory), + validate(WecInput.class, dataSource, wecInputFactory), + validate(EvcsInput.class, dataSource, evcsInputFactory)), "Validation") .transformF(FailedValidationException::new) .getOrThrow(); diff --git a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java index c83aa4e36..3d078f70b 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java @@ -29,7 +29,7 @@ * @version 0.1 * @since 08.04.20 */ -public class ThermalSource extends EntitySource { +public class ThermalSource extends AssetEntitySource { // general fields private final TypeSource typeSource; @@ -51,9 +51,9 @@ public ThermalSource(TypeSource typeSource, DataSource dataSource) { public void validate() throws ValidationException { Try.scanStream( Stream.of( - validate(ThermalBusInput.class, thermalBusInputFactory), - validate(CylindricalStorageInput.class, cylindricalStorageInputFactory), - validate(ThermalHouseInput.class, thermalHouseInputFactory)), + validate(ThermalBusInput.class, dataSource, thermalBusInputFactory), + validate(CylindricalStorageInput.class, dataSource, cylindricalStorageInputFactory), + validate(ThermalHouseInput.class, dataSource, thermalHouseInputFactory)), "Validation") .transformF(FailedValidationException::new) .getOrThrow(); diff --git a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java index aad5b2174..f481e5559 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesMappingSource.java @@ -7,6 +7,7 @@ import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.timeseries.TimeSeriesMappingFactory; import edu.ie3.datamodel.models.input.InputEntity; @@ -20,7 +21,7 @@ * This interface describes basic function to handle mapping between models and their respective * time series */ -public abstract class TimeSeriesMappingSource { +public abstract class TimeSeriesMappingSource extends EntitySource { protected final TimeSeriesMappingFactory mappingFactory; @@ -28,6 +29,11 @@ protected TimeSeriesMappingSource() { this.mappingFactory = new TimeSeriesMappingFactory(); } + @Override + public void validate() throws ValidationException { + validate(MappingEntry.class, this::getSourceFields, mappingFactory); + } + /** * Get a mapping from model {@link UUID} to the time series {@link UUID} * diff --git a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java index fe45ee751..994f0f440 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TimeSeriesSource.java @@ -21,10 +21,10 @@ * The interface definition of a source, that is able to provide one specific time series for one * model */ -public abstract class TimeSeriesSource { +public abstract class TimeSeriesSource extends EntitySource { protected Class valueClass; - protected TimeBasedSimpleValueFactory valueFactory; + protected final TimeBasedSimpleValueFactory valueFactory; protected TimeSeriesSource(Class valueClass, TimeBasedSimpleValueFactory factory) { this.valueFactory = factory; diff --git a/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java b/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java index d0263f87a..35e3edd29 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java @@ -46,8 +46,10 @@ public class TypeSource extends EntitySource { private final Transformer3WTypeInputFactory transformer3WTypeInputFactory; private final SystemParticipantTypeInputFactory systemParticipantTypeInputFactory; + private final DataSource dataSource; + public TypeSource(DataSource dataSource) { - super(dataSource); + this.dataSource = dataSource; this.operatorInputFactory = new OperatorInputFactory(); this.transformer2WTypeInputFactory = new Transformer2WTypeInputFactory(); @@ -67,15 +69,15 @@ public void validate() throws ValidationException { WecTypeInput.class, ChpTypeInput.class, StorageTypeInput.class) - .map(c -> validate(c, systemParticipantTypeInputFactory)) + .map(c -> validate(c, dataSource, systemParticipantTypeInputFactory)) .toList()); participantResults.addAll( List.of( - validate(OperatorInput.class, operatorInputFactory), - validate(LineTypeInput.class, lineTypeInputFactory), - validate(Transformer2WTypeInput.class, transformer2WTypeInputFactory), - validate(Transformer3WTypeInput.class, transformer3WTypeInputFactory))); + validate(OperatorInput.class, dataSource, operatorInputFactory), + validate(LineTypeInput.class, dataSource, lineTypeInputFactory), + validate(Transformer2WTypeInput.class, dataSource, transformer2WTypeInputFactory), + validate(Transformer3WTypeInput.class, dataSource, transformer3WTypeInputFactory))); Try.scanCollection(participantResults, Void.class) .transformF(FailedValidationException::new) @@ -94,7 +96,8 @@ public void validate() throws ValidationException { */ public Map getTransformer2WTypes() throws SourceException { return unpackMap( - buildEntityData(Transformer2WTypeInput.class).map(transformer2WTypeInputFactory::get), + buildEntityData(Transformer2WTypeInput.class, dataSource) + .map(transformer2WTypeInputFactory::get), Transformer2WTypeInput.class); } @@ -109,7 +112,8 @@ public Map getTransformer2WTypes() throws SourceEx */ public Map getOperators() throws SourceException { return unpackMap( - buildEntityData(OperatorInput.class).map(operatorInputFactory::get), OperatorInput.class); + buildEntityData(OperatorInput.class, dataSource).map(operatorInputFactory::get), + OperatorInput.class); } /** @@ -123,7 +127,8 @@ public Map getOperators() throws SourceException { */ public Map getLineTypes() throws SourceException { return unpackMap( - buildEntityData(LineTypeInput.class).map(lineTypeInputFactory::get), LineTypeInput.class); + buildEntityData(LineTypeInput.class, dataSource).map(lineTypeInputFactory::get), + LineTypeInput.class); } /** @@ -138,7 +143,8 @@ public Map getLineTypes() throws SourceException { */ public Map getTransformer3WTypes() throws SourceException { return unpackMap( - buildEntityData(Transformer3WTypeInput.class).map(transformer3WTypeInputFactory::get), + buildEntityData(Transformer3WTypeInput.class, dataSource) + .map(transformer3WTypeInputFactory::get), Transformer3WTypeInput.class); } @@ -240,6 +246,7 @@ public Map getEvTypes() throws SourceException { @SuppressWarnings("unchecked") private Stream> buildEntities( Class entityClass, EntityFactory factory) { - return buildEntityData(entityClass).map(data -> (Try) factory.get(data)); + return buildEntityData(entityClass, dataSource) + .map(data -> (Try) factory.get(data)); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java index 1cf672164..26ad09f87 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java @@ -6,6 +6,7 @@ package edu.ie3.datamodel.io.source; import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedWeatherValueData; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedWeatherValueFactory; import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries; @@ -22,7 +23,7 @@ import org.slf4j.LoggerFactory; /** Abstract class for WeatherSource by Csv and Sql Data */ -public abstract class WeatherSource { +public abstract class WeatherSource extends EntitySource { protected static final Logger log = LoggerFactory.getLogger(WeatherSource.class); @@ -43,11 +44,14 @@ protected WeatherSource( /** * Method to retrieve the fields found in the source. * - * @param entityClass class of the source * @return an option for fields found in the source */ - public abstract Optional> getSourceFields( - Class entityClass) throws SourceException; + public abstract Optional> getSourceFields() throws SourceException; + + @Override + public void validate() throws ValidationException { + validate(WeatherValue.class, this::getSourceFields, weatherFactory); + } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- diff --git a/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java index ac9ae8294..cf22278ea 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java @@ -19,7 +19,6 @@ import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries; import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.WeatherValue; -import edu.ie3.datamodel.utils.Try; import edu.ie3.util.interval.ClosedInterval; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; @@ -91,30 +90,16 @@ public CouchbaseWeatherSource( String coordinateIdColumnName, String keyPrefix, TimeBasedWeatherValueFactory weatherFactory, - String timeStampPattern) - throws SourceException { + String timeStampPattern) { super(idCoordinateSource, weatherFactory); this.connector = connector; this.coordinateIdColumnName = coordinateIdColumnName; this.keyPrefix = keyPrefix; this.timeStampPattern = timeStampPattern; - - // validating - Try.of(() -> getSourceFields(WeatherValue.class), SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - weatherFactory - .validate(fields, WeatherValue.class) - .transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); } @Override - public Optional> getSourceFields(Class entityClass) { + public Optional> getSourceFields() { return connector.getSourceFields(); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java index 7868c809e..647bcf02d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java @@ -36,7 +36,7 @@ * Implementation of {@link IdCoordinateSource} to read the mapping between coordinate id and actual * coordinate from csv file and build a mapping from it. */ -public class CsvIdCoordinateSource implements IdCoordinateSource { +public class CsvIdCoordinateSource extends IdCoordinateSource { protected static final Logger log = LoggerFactory.getLogger(CsvIdCoordinateSource.class); @@ -53,11 +53,21 @@ public CsvIdCoordinateSource(IdCoordinateFactory factory, CsvDataSource dataSour this.factory = factory; this.dataSource = dataSource; + // validating + Try.ofVoid(this::validate, ValidationException.class) + .transformF(SourceException::new) + .getOrThrow(); + /* set up the coordinate id to lat/long mapping */ idToCoordinate = setupIdToCoordinateMap(); coordinateToId = invert(idToCoordinate); } + @Override + public void validate() throws ValidationException { + validate(IdCoordinateInput.class, this::getSourceFields, factory); + } + /** * Read in and process the mapping * diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesMappingSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesMappingSource.java index 4f1065dd7..c6de18c1e 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesMappingSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesMappingSource.java @@ -8,7 +8,6 @@ import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.io.naming.FileNamingStrategy; import edu.ie3.datamodel.io.source.TimeSeriesMappingSource; -import edu.ie3.datamodel.utils.Try; import java.nio.file.Path; import java.util.Map; import java.util.Optional; @@ -20,22 +19,8 @@ public class CsvTimeSeriesMappingSource extends TimeSeriesMappingSource { private final CsvDataSource dataSource; public CsvTimeSeriesMappingSource( - String csvSep, Path gridFolderPath, FileNamingStrategy fileNamingStrategy) - throws SourceException { + String csvSep, Path gridFolderPath, FileNamingStrategy fileNamingStrategy) { this.dataSource = new CsvDataSource(csvSep, gridFolderPath, fileNamingStrategy); - - // validating - Try.of(this::getSourceFields, SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - mappingFactory - .validate(fields, MappingEntry.class) - .transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); } @Override diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java index 49d6f83e5..d7de7904a 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java @@ -7,6 +7,7 @@ import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.csv.CsvIndividualTimeSeriesMetaInformation; import edu.ie3.datamodel.io.factory.timeseries.*; import edu.ie3.datamodel.io.naming.FileNamingStrategy; @@ -27,6 +28,7 @@ public class CsvTimeSeriesSource extends TimeSeriesSource { private final IndividualTimeSeries timeSeries; private final CsvDataSource dataSource; + private final Path filePath; /** * Factory method to build a source from given meta information @@ -58,7 +60,8 @@ private static CsvTimeSeriesSource create( Path folderPath, FileNamingStrategy fileNamingStrategy, CsvIndividualTimeSeriesMetaInformation metaInformation, - Class valClass) { + Class valClass) + throws SourceException { TimeBasedSimpleValueFactory valueFactory = new TimeBasedSimpleValueFactory<>(valClass); return new CsvTimeSeriesSource<>( csvSep, @@ -88,10 +91,17 @@ public CsvTimeSeriesSource( UUID timeSeriesUuid, Path filePath, Class valueClass, - TimeBasedSimpleValueFactory factory) { + TimeBasedSimpleValueFactory factory) + throws SourceException { super(valueClass, factory); this.dataSource = new CsvDataSource(csvSep, folderPath, fileNamingStrategy); + // validate + this.filePath = filePath; + Try.ofVoid(this::validate, ValidationException.class) + .transformF(SourceException::new) + .getOrThrow(); + /* Read in the full time series */ try { this.timeSeries = @@ -105,6 +115,11 @@ public CsvTimeSeriesSource( } } + @Override + public void validate() throws ValidationException { + validate(valueClass, () -> dataSource.getSourceFields(filePath), valueFactory); + } + @Override public IndividualTimeSeries getTimeSeries() { return timeSeries; diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java index fbf78b4f9..3558051a7 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java @@ -64,6 +64,12 @@ public CsvWeatherSource( throws SourceException { super(idCoordinateSource, weatherFactory); this.dataSource = new CsvDataSource(csvSep, folderPath, fileNamingStrategy); + + // validating + Try.ofVoid(this::validate, ValidationException.class) + .transformF(SourceException::new) + .getOrThrow(); + coordinateToTimeSeries = getWeatherTimeSeries(); } @@ -71,8 +77,21 @@ public CsvWeatherSource( /** Returns an empty optional for now. */ @Override - public Optional> getSourceFields(Class entityClass) { - return Optional.empty(); + public Optional> getSourceFields() { + return dataSource + .connector + .getCsvIndividualTimeSeriesMetaInformation(ColumnScheme.WEATHER) + .values() + .stream() + .findFirst() + .flatMap( + meta -> { + try { + return dataSource.getSourceFields(meta.getFullFilePath()); + } catch (SourceException e) { + return Optional.empty(); + } + }); } @Override diff --git a/src/main/java/edu/ie3/datamodel/io/source/influxdb/InfluxDbWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/influxdb/InfluxDbWeatherSource.java index 2d9994d8a..54531826b 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/influxdb/InfluxDbWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/influxdb/InfluxDbWeatherSource.java @@ -5,7 +5,6 @@ */ package edu.ie3.datamodel.io.source.influxdb; -import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.io.connectors.InfluxDbConnector; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedWeatherValueData; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedWeatherValueFactory; @@ -48,26 +47,13 @@ public class InfluxDbWeatherSource extends WeatherSource { public InfluxDbWeatherSource( InfluxDbConnector connector, IdCoordinateSource idCoordinateSource, - TimeBasedWeatherValueFactory weatherValueFactory) - throws SourceException { + TimeBasedWeatherValueFactory weatherValueFactory) { super(idCoordinateSource, weatherValueFactory); this.connector = connector; - - Try.of(() -> getSourceFields(WeatherValue.class), SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - weatherFactory - .validate(fields, WeatherValue.class) - .transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); } @Override - public Optional> getSourceFields(Class entityClass) { + public Optional> getSourceFields() { return connector.getSourceFields(); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlIdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlIdCoordinateSource.java index 387039266..3fcd92ec2 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlIdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlIdCoordinateSource.java @@ -7,7 +7,7 @@ import static edu.ie3.datamodel.io.source.sql.SqlDataSource.createBaseQueryString; -import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.connectors.SqlConnector; import edu.ie3.datamodel.io.factory.SimpleFactoryData; import edu.ie3.datamodel.io.factory.timeseries.SqlIdCoordinateFactory; @@ -15,7 +15,6 @@ import edu.ie3.datamodel.io.source.IdCoordinateSource; import edu.ie3.datamodel.models.input.IdCoordinateInput; import edu.ie3.datamodel.models.value.CoordinateValue; -import edu.ie3.datamodel.utils.Try; import edu.ie3.util.geo.CoordinateDistance; import edu.ie3.util.geo.GeoUtils; import java.sql.Array; @@ -27,7 +26,7 @@ import tech.units.indriya.ComparableQuantity; /** SQL source for coordinate data */ -public class SqlIdCoordinateSource implements IdCoordinateSource { +public class SqlIdCoordinateSource extends IdCoordinateSource { private static final String WHERE = " WHERE "; /** @@ -48,8 +47,7 @@ public class SqlIdCoordinateSource implements IdCoordinateSource { private final SqlIdCoordinateFactory factory; public SqlIdCoordinateSource( - SqlIdCoordinateFactory factory, String coordinateTableName, SqlDataSource dataSource) - throws SourceException { + SqlIdCoordinateFactory factory, String coordinateTableName, SqlDataSource dataSource) { this.factory = factory; this.dataSource = dataSource; this.coordinateTableName = coordinateTableName; @@ -58,19 +56,6 @@ public SqlIdCoordinateSource( String dbPointColumnName = dataSource.getDbColumnName(factory.getCoordinateField(), coordinateTableName); - // validating table - Try.of(this::getSourceFields, SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - factory - .validate(fields, IdCoordinateInput.class) - .transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); - // setup queries this.basicQuery = createBaseQueryString(dataSource.schemaName, coordinateTableName); this.queryForPoint = createQueryForPoint(dbIdColumnName); @@ -94,14 +79,18 @@ public SqlIdCoordinateSource( SqlConnector connector, String schemaName, String coordinateTableName, - SqlIdCoordinateFactory factory) - throws SourceException { + SqlIdCoordinateFactory factory) { this( factory, coordinateTableName, new SqlDataSource(connector, schemaName, new DatabaseNamingStrategy())); } + @Override + public void validate() throws ValidationException { + validate(IdCoordinateInput.class, this::getSourceFields, factory); + } + @Override public Optional> getSourceFields() { return dataSource.getSourceFields(coordinateTableName); diff --git a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesMappingSource.java b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesMappingSource.java index 6b37bd173..410290967 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesMappingSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesMappingSource.java @@ -12,7 +12,6 @@ import edu.ie3.datamodel.io.naming.DatabaseNamingStrategy; import edu.ie3.datamodel.io.naming.EntityPersistenceNamingStrategy; import edu.ie3.datamodel.io.source.TimeSeriesMappingSource; -import edu.ie3.datamodel.utils.Try; import java.util.Map; import java.util.Optional; import java.util.Set; @@ -27,8 +26,7 @@ public class SqlTimeSeriesMappingSource extends TimeSeriesMappingSource { public SqlTimeSeriesMappingSource( SqlConnector connector, String schemaName, - EntityPersistenceNamingStrategy entityPersistenceNamingStrategy) - throws SourceException { + EntityPersistenceNamingStrategy entityPersistenceNamingStrategy) { this.dataSource = new SqlDataSource( connector, schemaName, new DatabaseNamingStrategy(entityPersistenceNamingStrategy)); @@ -37,18 +35,6 @@ public SqlTimeSeriesMappingSource( this.tableName = entityPersistenceNamingStrategy.getEntityName(MappingEntry.class).orElseThrow(); this.queryFull = createBaseQueryString(schemaName, tableName); - - Try.of(this::getSourceFields, SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - mappingFactory - .validate(fields, MappingEntry.class) - .transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); } @Override diff --git a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java index c2950911d..da725ea04 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlTimeSeriesSource.java @@ -8,6 +8,7 @@ import static edu.ie3.datamodel.io.source.sql.SqlDataSource.createBaseQueryString; import edu.ie3.datamodel.exceptions.SourceException; +import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.connectors.SqlConnector; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedSimpleValueFactory; import edu.ie3.datamodel.io.naming.DatabaseNamingStrategy; @@ -18,12 +19,14 @@ import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.Value; import edu.ie3.datamodel.utils.TimeSeriesUtils; -import edu.ie3.datamodel.utils.Try; import edu.ie3.util.interval.ClosedInterval; import java.sql.Timestamp; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; -import java.util.*; +import java.util.Map; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.stream.Collectors; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -32,6 +35,7 @@ public class SqlTimeSeriesSource extends TimeSeriesSource { protected static final Logger log = LoggerFactory.getLogger(SqlTimeSeriesSource.class); private final SqlDataSource dataSource; + private final String tableName; private final UUID timeSeriesUuid; @@ -52,29 +56,14 @@ public SqlTimeSeriesSource( SqlDataSource sqlDataSource, UUID timeSeriesUuid, Class valueClass, - TimeBasedSimpleValueFactory factory) - throws SourceException { + TimeBasedSimpleValueFactory factory) { super(valueClass, factory); this.dataSource = sqlDataSource; this.timeSeriesUuid = timeSeriesUuid; - this.valueClass = valueClass; - this.valueFactory = factory; - final ColumnScheme columnScheme = ColumnScheme.parse(valueClass).orElseThrow(); - final String tableName = - sqlDataSource.databaseNamingStrategy.getTimeSeriesEntityName(columnScheme); - - Try.of(() -> dataSource.getSourceFields(tableName), SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - factory.validate(fields, valueClass).transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); + this.tableName = sqlDataSource.databaseNamingStrategy.getTimeSeriesEntityName(columnScheme); String dbTimeColumnName = sqlDataSource.getDbColumnName(factory.getTimeFieldString(), tableName); @@ -101,8 +90,7 @@ public SqlTimeSeriesSource( DatabaseNamingStrategy namingStrategy, UUID timeSeriesUuid, Class valueClass, - TimeBasedSimpleValueFactory factory) - throws SourceException { + TimeBasedSimpleValueFactory factory) { this( new SqlDataSource(connector, schemaName, namingStrategy), timeSeriesUuid, @@ -110,6 +98,11 @@ public SqlTimeSeriesSource( factory); } + @Override + public void validate() throws ValidationException { + validate(valueClass, () -> dataSource.getSourceFields(tableName), valueFactory); + } + /** * Factory method to build a source from given meta information * @@ -149,8 +142,7 @@ private static SqlTimeSeriesSource create( DatabaseNamingStrategy namingStrategy, UUID timeSeriesUuid, Class valClass, - DateTimeFormatter dateTimeFormatter) - throws SourceException { + DateTimeFormatter dateTimeFormatter) { TimeBasedSimpleValueFactory valueFactory = new TimeBasedSimpleValueFactory<>(valClass, dateTimeFormatter); return new SqlTimeSeriesSource<>( diff --git a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlWeatherSource.java index 37ac91c2e..1973a57ec 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/sql/SqlWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/sql/SqlWeatherSource.java @@ -16,9 +16,9 @@ import edu.ie3.datamodel.models.timeseries.individual.IndividualTimeSeries; import edu.ie3.datamodel.models.timeseries.individual.TimeBasedValue; import edu.ie3.datamodel.models.value.WeatherValue; -import edu.ie3.datamodel.utils.Try; import edu.ie3.util.interval.ClosedInterval; -import java.sql.*; +import java.sql.Array; +import java.sql.Timestamp; import java.time.ZonedDateTime; import java.util.*; import java.util.stream.Collectors; @@ -30,7 +30,6 @@ public class SqlWeatherSource extends WeatherSource { private final SqlDataSource dataSource; private static final String WHERE = " WHERE "; - private final String factoryCoordinateFieldName; private final String tableName; /** @@ -56,25 +55,12 @@ public SqlWeatherSource( IdCoordinateSource idCoordinateSource, String schemaName, String weatherTableName, - TimeBasedWeatherValueFactory weatherFactory) - throws SourceException { + TimeBasedWeatherValueFactory weatherFactory) { super(idCoordinateSource, weatherFactory); - this.factoryCoordinateFieldName = weatherFactory.getCoordinateIdFieldString(); + String factoryCoordinateFieldName = weatherFactory.getCoordinateIdFieldString(); this.dataSource = new SqlDataSource(connector, schemaName, new DatabaseNamingStrategy()); this.tableName = weatherTableName; - Try.of(() -> getSourceFields(WeatherValue.class), SourceException.class) - .flatMap( - fieldsOpt -> - fieldsOpt - .map( - fields -> - weatherFactory - .validate(fields, WeatherValue.class) - .transformF(SourceException::new)) - .orElse(Try.Success.empty())) - .getOrThrow(); - String dbTimeColumnName = dataSource.getDbColumnName(weatherFactory.getTimeFieldString(), weatherTableName); String dbCoordinateIdColumnName = @@ -92,7 +78,7 @@ public SqlWeatherSource( } @Override - public Optional> getSourceFields(Class entityClass) { + public Optional> getSourceFields() { return dataSource.getSourceFields(tableName); } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy similarity index 98% rename from src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy rename to src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy index f5cbc5bc0..4a2d57c81 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy @@ -28,9 +28,9 @@ import edu.ie3.test.common.SystemParticipantTestData as sptd import spock.lang.Shared import spock.lang.Specification -class EntitySourceTest extends Specification { +class AssetEntitySourceTest extends Specification { - private final class DummyEntitySource extends EntitySource { + private final class DummyEntitySource extends AssetEntitySource { DummyEntitySource(CsvDataSource dataSource) { super(dataSource) } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/IdCoordinateSourceMock.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/IdCoordinateSourceMock.groovy index bdbe1fea6..621c6613c 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/IdCoordinateSourceMock.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/IdCoordinateSourceMock.groovy @@ -6,13 +6,14 @@ package edu.ie3.datamodel.io.source import edu.ie3.datamodel.exceptions.SourceException +import edu.ie3.datamodel.exceptions.ValidationException import edu.ie3.util.geo.CoordinateDistance import org.locationtech.jts.geom.Point import tech.units.indriya.ComparableQuantity import javax.measure.quantity.Length -class IdCoordinateSourceMock implements IdCoordinateSource { +class IdCoordinateSourceMock extends IdCoordinateSource { @Override Optional> getSourceFields() throws SourceException { @@ -48,4 +49,8 @@ class IdCoordinateSourceMock implements IdCoordinateSource { List getClosestCoordinates(Point coordinate, int n, ComparableQuantity distance) { return Collections.emptyList() } + + @Override + void validate() throws ValidationException { + } } diff --git a/src/test/groovy/edu/ie3/test/common/WeatherTestData.groovy b/src/test/groovy/edu/ie3/test/common/WeatherTestData.groovy index 343af527a..41a84d39f 100644 --- a/src/test/groovy/edu/ie3/test/common/WeatherTestData.groovy +++ b/src/test/groovy/edu/ie3/test/common/WeatherTestData.groovy @@ -6,6 +6,7 @@ package edu.ie3.test.common import edu.ie3.datamodel.exceptions.SourceException +import edu.ie3.datamodel.exceptions.ValidationException import edu.ie3.datamodel.io.source.IdCoordinateSource import edu.ie3.datamodel.io.source.csv.CsvTestDataMeta import edu.ie3.util.geo.CoordinateDistance @@ -19,7 +20,7 @@ import javax.measure.quantity.Length abstract class WeatherTestData { - static final class DummyIdCoordinateSource implements CsvTestDataMeta, IdCoordinateSource { + static final class DummyIdCoordinateSource extends IdCoordinateSource implements CsvTestDataMeta { @Override Optional> getSourceFields() throws SourceException { @@ -80,6 +81,10 @@ abstract class WeatherTestData { List getClosestCoordinates(Point coordinate, int n, ComparableQuantity distance) { throw new UnsupportedOperationException("This method is not supported!") } + + @Override + void validate() throws ValidationException { + } } public static final IdCoordinateSource coordinateSource = new DummyIdCoordinateSource() From ce8bb07c27f1db6fd45ebc038f6e25b516832059 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 11 Mar 2024 12:58:42 +0100 Subject: [PATCH 02/22] Abstract more functionality. --- .../input/AssetInputEntityFactory.java | 1 + .../input/ConnectorInputEntityFactory.java | 3 +- .../input/Transformer3WInputEntityData.java | 15 + .../factory/result/ResultEntityFactory.java | 5 +- .../io/source/AssetEntitySource.java | 388 ++++-------------- .../io/source/EnergyManagementSource.java | 9 +- .../ie3/datamodel/io/source/EntitySource.java | 305 +++++++++++++- .../datamodel/io/source/GraphicSource.java | 84 +--- .../datamodel/io/source/RawGridSource.java | 173 +++----- .../io/source/ResultEntitySource.java | 13 +- .../io/source/SystemParticipantSource.java | 287 +++++-------- .../datamodel/io/source/ThermalSource.java | 84 ++-- .../ie3/datamodel/io/source/TypeSource.java | 58 +-- .../edu/ie3/datamodel/utils/QuadFunction.java | 50 +++ .../java/edu/ie3/datamodel/utils/Try.java | 34 ++ 15 files changed, 710 insertions(+), 799 deletions(-) create mode 100644 src/main/java/edu/ie3/datamodel/utils/QuadFunction.java diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java index a5797cf9b..b9a104137 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/AssetInputEntityFactory.java @@ -27,6 +27,7 @@ public abstract class AssetInputEntityFactory... allowedClasses) { super(allowedClasses); } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/ConnectorInputEntityFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/input/ConnectorInputEntityFactory.java index cc29c7e5e..8fc59cbec 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/ConnectorInputEntityFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/ConnectorInputEntityFactory.java @@ -20,7 +20,7 @@ * @param Type of data class that is required for entity creation * @since 19.02.20 */ -abstract class ConnectorInputEntityFactory< +public abstract class ConnectorInputEntityFactory< T extends ConnectorInput, D extends ConnectorInputEntityData> extends AssetInputEntityFactory { @@ -31,6 +31,7 @@ abstract class ConnectorInputEntityFactory< */ protected static final String PARALLEL_DEVICES = "parallelDevices"; + @SafeVarargs protected ConnectorInputEntityFactory(Class... allowedClasses) { super(allowedClasses); } diff --git a/src/main/java/edu/ie3/datamodel/io/factory/input/Transformer3WInputEntityData.java b/src/main/java/edu/ie3/datamodel/io/factory/input/Transformer3WInputEntityData.java index e53678f1a..deed94e8e 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/input/Transformer3WInputEntityData.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/input/Transformer3WInputEntityData.java @@ -39,6 +39,21 @@ public Transformer3WInputEntityData( this.nodeC = nodeC; } + /** + * Creates a new Transformer3WInputEntityData object based on a given {@link + * ConnectorInputEntityData} object, a given third node as well as a given {@link + * Transformer3WTypeInput}. + * + * @param entityData The TypedConnectorInputEntityData object to enhance + * @param nodeC The third node + * @param type of the transformer + */ + public Transformer3WInputEntityData( + ConnectorInputEntityData entityData, NodeInput nodeC, Transformer3WTypeInput type) { + super(entityData, type); + this.nodeC = nodeC; + } + /** * Creates a new Transformer3WInputEntityData object based on a given {@link * TypedConnectorInputEntityData} object and given third node diff --git a/src/main/java/edu/ie3/datamodel/io/factory/result/ResultEntityFactory.java b/src/main/java/edu/ie3/datamodel/io/factory/result/ResultEntityFactory.java index f1c1d0495..6eadf1cd9 100644 --- a/src/main/java/edu/ie3/datamodel/io/factory/result/ResultEntityFactory.java +++ b/src/main/java/edu/ie3/datamodel/io/factory/result/ResultEntityFactory.java @@ -18,18 +18,21 @@ * @version 0.1 * @since 11.02.20 */ -abstract class ResultEntityFactory extends EntityFactory { +public abstract class ResultEntityFactory + extends EntityFactory { protected static final String TIME = "time"; protected static final String INPUT_MODEL = "inputModel"; protected final TimeUtil timeUtil; + @SafeVarargs protected ResultEntityFactory(Class... allowedClasses) { super(allowedClasses); timeUtil = TimeUtil.withDefaults; } + @SafeVarargs protected ResultEntityFactory( DateTimeFormatter dateTimeFormatter, Class... allowedClasses) { super(allowedClasses); diff --git a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java index 72ecf628b..80b4dfe94 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java @@ -5,30 +5,24 @@ */ package edu.ie3.datamodel.io.source; -import edu.ie3.datamodel.exceptions.FactoryException; +import static edu.ie3.datamodel.models.input.OperatorInput.NO_OPERATOR_ASSIGNED; + import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.io.factory.EntityData; -import edu.ie3.datamodel.io.factory.EntityFactory; -import edu.ie3.datamodel.io.factory.input.AssetInputEntityData; -import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData; -import edu.ie3.datamodel.models.Entity; -import edu.ie3.datamodel.models.UniqueEntity; -import edu.ie3.datamodel.models.input.AssetInput; +import edu.ie3.datamodel.io.factory.input.*; +import edu.ie3.datamodel.io.factory.input.participant.SystemParticipantEntityData; +import edu.ie3.datamodel.models.input.AssetTypeInput; import edu.ie3.datamodel.models.input.NodeInput; import edu.ie3.datamodel.models.input.OperatorInput; -import edu.ie3.datamodel.utils.TriFunction; +import edu.ie3.datamodel.models.input.connector.ConnectorInput; import edu.ie3.datamodel.utils.Try; -import edu.ie3.datamodel.utils.Try.Failure; -import edu.ie3.datamodel.utils.Try.Success; import java.util.Map; -import java.util.Optional; import java.util.UUID; -import java.util.function.BiFunction; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -/** Class that provides all functionalities to build entities */ +/** Class that provides all functionalities to build asset entities */ public abstract class AssetEntitySource extends EntitySource { protected static final Logger log = LoggerFactory.getLogger(AssetEntitySource.class); @@ -38,8 +32,46 @@ public abstract class AssetEntitySource extends EntitySource { // field names protected static final String OPERATOR = "operator"; protected static final String NODE = "node"; + protected static final String NODE_A = "nodeA"; + protected static final String NODE_B = "nodeB"; protected static final String TYPE = "type"; + // enriching functions + protected static final EnrichFunction + assetEnricher = + (data, operators) -> + enrich( + data, + buildEnrichmentWithDefault(data, OPERATOR, operators, NO_OPERATOR_ASSIGNED), + AssetInputEntityData::new); + + protected static final BiEnrichFunction< + EntityData, OperatorInput, NodeInput, NodeAssetInputEntityData> + nodeAssetEnricher = + (data, operators, nodes) -> + assetEnricher + .andThen( + enrichedData -> + enrich( + enrichedData, + buildEnrichment(enrichedData, NODE, nodes), + NodeAssetInputEntityData::new)) + .apply(data, operators); + + protected static final BiEnrichFunction< + EntityData, OperatorInput, NodeInput, ConnectorInputEntityData> + connectorEnricher = + (data, operators, nodes) -> + assetEnricher + .andThen( + assetData -> + biEnrich( + assetData, + buildEnrichment(data, NODE_A, nodes), + buildEnrichment(data, NODE_B, nodes), + ConnectorInputEntityData::new)) + .apply(data, operators); + protected AssetEntitySource(DataSource dataSource) { this.dataSource = dataSource; } @@ -47,305 +79,49 @@ protected AssetEntitySource(DataSource dataSource) { // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= /** - * Enhances given entity data with an entity from the given entity map. The linked entity is - * chosen by taking into account the UUID found by retrieving the field with given fieldName from - * entityData. - * - * @param entityData The entity data to be enhanced, which also provides a link to another entity - * via UUID - * @param fieldName The field name of the field that provides the UUID of the linked entity - * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity - * data - * @param createEntityData The function that creates the resulting entity data given entityData - * and the linked entity - * @param Type of input entity data - * @param Type of the linked entity - * @param Type of resulting entity data that combines the given entityData and linked entity - * @return {@link Try} to enhanced data - */ - protected static - Try enrichEntityData( - E entityData, - String fieldName, - Map linkedEntities, - BiFunction createEntityData) { - return getLinkedEntity(entityData, fieldName, linkedEntities) - .map( - linkedEntity -> { - Map fieldsToAttributes = entityData.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName); - - // build resulting entity data - return createEntityData.apply(entityData, linkedEntity); - }); - } - - /** - * Enhances given entity data with two entities from the given entity maps. The linked entities - * are chosen by taking into account the UUIDs found by retrieving the fields with given - * fieldName1 and fieldName2 from entityData. - * - * @param entityData The entity data to be enhanced, which also provides links to two other - * entities via UUID - * @param fieldName1 The field name of the field that provides the UUID of the first linked entity - * @param linkedEntities1 The first map of UUID to entities, of which one should be linked to - * given entity data - * @param fieldName2 The field name of the field that provides the UUID of the second linked - * entity - * @param linkedEntities2 The second map of UUID to entities, of which one should be linked to - * given entity data - * @param createEntityData The function that creates the resulting entity data given entityData - * and the linked entities - * @param Type of input entity data - * @param Type of the first linked entity - * @param Type of the second linked entity - * @param Type of resulting entity data that combines the given entityData and two linked - * entities - * @return {@link Try} to enhanced data - */ - protected static < - E extends EntityData, T1 extends UniqueEntity, T2 extends UniqueEntity, R extends E> - Try enrichEntityData( - E entityData, - String fieldName1, - Map linkedEntities1, - String fieldName2, - Map linkedEntities2, - TriFunction createEntityData) { - return getLinkedEntity(entityData, fieldName1, linkedEntities1) - .flatMap( - linkedEntity1 -> - getLinkedEntity(entityData, fieldName2, linkedEntities2) - .map( - linkedEntity2 -> { - Map fieldsToAttributes = entityData.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName1); - fieldsToAttributes.keySet().remove(fieldName2); - - // build resulting entity data - return createEntityData.apply(entityData, linkedEntity1, linkedEntity2); - })); - } - - /** - * Checks if the linked entity can be found in the provided map of entities. The linked entities - * are chosen by taking into account the UUIDs found by retrieving the fields with given - * fieldName1 and fieldName2 from entityData. - * - * @param entityData The entity data of the entity that provides a link to another entity via UUID - * @param fieldName The field name of the field that provides the UUID of the linked entity - * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity - * data - * @param the type of the resulting linked entity instance - * @return a {@link Success} containing the entity or a {@link Failure} if the entity cannot be - * found - */ - protected static Try getLinkedEntity( - EntityData entityData, String fieldName, Map linkedEntities) { - - return Try.of(() -> entityData.getUUID(fieldName), FactoryException.class) - .transformF( - exception -> - new SourceException( - "Extracting UUID field " - + fieldName - + " from entity data " - + entityData.toString() - + " failed.", - exception)) - .flatMap( - entityUuid -> - getEntity(entityUuid, linkedEntities) - .transformF( - exception -> - new SourceException( - "Linked " - + fieldName - + " with UUID " - + entityUuid - + " was not found for entity " - + entityData, - exception))); - } - - /** - * Enhances given entity data with an entity from the given entity map or the default value. The - * linked entity is possibly chosen by taking into account the UUID found by retrieving the field - * with given fieldName from entityData. If no entity is linked, the default value is used. - * - * @param entityData The entity data to be enhanced, which also might provide a link to another - * entity via UUID - * @param fieldName The field name of the field that might provide the UUID of the linked entity - * @param linkedEntities A map of UUID to entities, of which one should be linked to given entity - * data - * @param defaultEntity The default linked entity to use, if no actual linked entity could be - * found - * @param createEntityData The function that creates the resulting entity data given entityData - * and the linked entity (either retrieved from the map or the standard entity) - * @param Type of input entity data - * @param Type of the linked entity - * @param Type of resulting entity data that combines the given entityData and linked entity - * @return {@link Try} to enhanced data - */ - protected static - Try optionallyEnrichEntityData( - E entityData, - String fieldName, - Map linkedEntities, - T defaultEntity, - BiFunction createEntityData) { - return entityData - .getFieldOptional(fieldName) - .filter(s -> !s.isBlank()) - .map( - // Entity data includes a non-empty UUID String for the desired entity - uuidString -> - Try.of(() -> UUID.fromString(uuidString), IllegalArgumentException.class) - .transformF( - iae -> - // Parsing error still results in a failure, ... - new SourceException( - String.format( - "Exception while trying to parse UUID of field \"%s\" with value \"%s\"", - fieldName, uuidString), - iae)) - .flatMap( - entityUuid -> - getEntity(entityUuid, linkedEntities) - // ... as well as a provided entity UUID that does not match any - // given data - .transformF( - exception -> - new SourceException( - "Linked " - + fieldName - + " with UUID " - + entityUuid - + " was not found for entity " - + entityData, - exception)))) - .orElseGet( - () -> { - // No UUID was given (column does not exist, or field is empty). - // This is totally fine - we successfully return the default value - log.debug( - "Input source for class {} is missing the '{}' field. " - + "Default value '{}' is used.", - entityData.getTargetClass().getSimpleName(), - fieldName, - defaultEntity); - return new Try.Success<>(defaultEntity); - }) - .map( - linkedEntity -> { - Map fieldsToAttributes = entityData.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName); - - // build resulting entity data - return createEntityData.apply(entityData, linkedEntity); - }); - } - - private static Try getEntity(UUID uuid, Map entityMap) { - return Optional.ofNullable(entityMap.get(uuid)) - // We either find a matching entity for given UUID, thus return a success - .map(entity -> Try.of(() -> entity, SourceException.class)) - // ... or find no matching entity, returning a failure. - .orElse( - new Try.Failure<>( - new SourceException("Entity with uuid " + uuid + " was not provided."))); - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - /** - * Returns a stream of {@link Try} entities that can be built by using {@link - * NodeAssetInputEntityData} and their corresponding factory. - * - * @param entityClass the entity class that should be build - * @param nodes a map of UUID to {@link NodeInput} entities that should be used to build the - * entities - * @param operators a map of UUID to {@link OperatorInput} entities should be used to build the - * entities - * @return stream of tries of the entities that has been built by the factory - */ - protected Stream> buildNodeAssetEntityData( - Class entityClass, - Map operators, - Map nodes) { - return nodeAssetInputEntityDataStream(buildAssetInputEntityData(entityClass, operators), nodes); - } - - /** - * Returns a stream of tries of {@link NodeAssetInputEntityData} that can be used to build - * instances of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} - * that consumes this data. - * - * @param assetInputEntityDataStream a stream consisting of {@link AssetInputEntityData} that is - * enriched with {@link NodeInput} data - * @param nodes a map of UUID to {@link NodeInput} entities that should be used to build the data - * @return stream of the entity data wrapped in a {@link Try} - */ - protected static Stream> - nodeAssetInputEntityDataStream( - Stream> assetInputEntityDataStream, - Map nodes) { - return assetInputEntityDataStream - .parallel() - .map( - assetInputEntityDataTry -> - assetInputEntityDataTry.flatMap( - assetInputEntityData -> - enrichEntityData( - assetInputEntityData, NODE, nodes, NodeAssetInputEntityData::new))); - } - - /** - * Returns a stream of optional {@link AssetInputEntityData} that can be used to build instances - * of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} that - * consumes this data. + * Method to build typed connector entities. * - * @param entityClass the entity class that should be build - * @param operators a map of UUID to {@link OperatorInput} entities that should be used to build - * the data - * @return stream of the entity data wrapped in a {@link Try} + * @param entityClass class of the entity + * @param dataSource source for the data + * @param factory to build the entity + * @param operators map: uuid to {@link OperatorInput} + * @param nodes map: uuid to {@link NodeInput} + * @param types map: uuid to {@link AssetTypeInput} + * @return a stream of {@link ConnectorInput}s + * @param type of connector input + * @param type of asset types + * @throws SourceException if an error happens during reading */ - protected Stream> buildAssetInputEntityData( - Class entityClass, Map operators) { - return assetInputEntityDataStream(buildEntityData(entityClass, dataSource), operators); + protected + Stream getTypedConnectorEntities( + Class entityClass, + DataSource dataSource, + ConnectorInputEntityFactory> factory, + Map operators, + Map nodes, + Map types) + throws SourceException { + return getEntities( + entityClass, + dataSource, + factory, + data -> + connectorEnricher + .andThen(connectorData -> enrich(connectorData, types)) + .apply(data, operators, nodes)); } /** - * Returns a stream of tries of {@link AssetInputEntityData} that can be used to build instances - * of several subtypes of {@link UniqueEntity} by a corresponding {@link EntityFactory} that - * consumes this data. + * Method for enriching {@link SystemParticipantEntityData} with types. * - * @param entityDataStream a stream consisting of {@link EntityData} that is enriched with {@link - * OperatorInput} data - * @param operators map of UUID to {@link OperatorInput} entities that should be used to build the - * data - * @return stream of the entity data wrapped in a {@link Try} + * @param data to enrich + * @param types all known types + * @return a typed entity data + * @param type of types */ - protected static Stream> assetInputEntityDataStream( - Stream> entityDataStream, - Map operators) { - return entityDataStream - .parallel() - .map( - entityDataTry -> - entityDataTry.flatMap( - entityData -> - optionallyEnrichEntityData( - entityData, - OPERATOR, - operators, - OperatorInput.NO_OPERATOR_ASSIGNED, - AssetInputEntityData::new))); + private static + Try, SourceException> enrich( + Try data, Map types) { + return enrich(data, buildEnrichment(data, TYPE, types), TypedConnectorInputEntityData::new); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java index 9172aba09..b5ad92c5a 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java @@ -68,14 +68,15 @@ public Map getEmUnits() throws SourceException { * @return a map of UUID to {@link EmInput} entities */ public Map getEmUnits(Map operators) throws SourceException { - return createEmInputs(buildAssetInputEntityData(EmInput.class, operators)); + return createEmInputs( + buildEntityData(EmInput.class, dataSource, data -> assetEnricher.apply(data, operators))); } /** * Since each EM can itself be controlled by another EM, it does not suffice to link {@link - * EmInput}s via {@link AssetEntitySource#optionallyEnrichEntityData} as we do for system - * participants in {@link SystemParticipantSource}. Instead, we use a recursive approach, starting - * with EMs at root level (which are not EM-controlled themselves). + * EmInput}s via {@link EntitySource#buildEnrichmentWithDefault} as we do for system participants + * in {@link SystemParticipantSource}. Instead, we use a recursive approach, starting with EMs at + * root level (which are not EM-controlled themselves). * * @param assetEntityDataStream the data stream of {@link AssetInputEntityData} {@link Try} * objects diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index 83f09e0a0..21e9b1fd9 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -13,20 +13,38 @@ import edu.ie3.datamodel.io.factory.EntityFactory; import edu.ie3.datamodel.models.Entity; import edu.ie3.datamodel.models.UniqueEntity; +import edu.ie3.datamodel.utils.QuadFunction; +import edu.ie3.datamodel.utils.TriFunction; import edu.ie3.datamodel.utils.Try; +import edu.ie3.datamodel.utils.Try.Failure; import java.util.Map; import java.util.Optional; import java.util.Set; import java.util.UUID; +import java.util.function.BiFunction; import java.util.function.Function; +import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * Base class for all entity sources. This class provides some functionalities that are common among * sources. */ public abstract class EntitySource { + protected static final Logger log = LoggerFactory.getLogger(EntitySource.class); + + // default collectors + + protected static Collector> toMap() { + return Collectors.toMap(UniqueEntity::getUuid, Function.identity()); + } + + protected static Set toSet(Stream stream) { + return stream.collect(Collectors.toSet()); + } protected EntitySource() {} @@ -79,14 +97,14 @@ protected final Try validate( } /** - * Returns a stream of optional {@link EntityData} that can be used to build instances of several - * subtypes of {@link Entity} by a corresponding {@link EntityFactory} that consumes this data. + * Returns a stream of {@link EntityData} that can be used to build instances of several subtypes + * of {@link Entity} by a corresponding {@link EntityFactory} that consumes this data. * * @param entityClass the entity class that should be build * @param dataSource source for the data * @return a stream of the entity data wrapped in a {@link Try} */ - protected Stream> buildEntityData( + protected static Stream> buildEntityData( Class entityClass, DataSource dataSource) { return Try.of(() -> dataSource.getSourceData(entityClass), SourceException.class) .convert( @@ -94,18 +112,72 @@ protected Stream> buildEntityData( data.map( fieldsToAttributes -> new Try.Success<>(new EntityData(fieldsToAttributes, entityClass))), - exception -> Stream.of(Try.Failure.of(exception))); + exception -> Stream.of(Failure.of(exception))); } - protected static Map unpackMap( - Stream> inputStream, Class entityClass) throws SourceException { - return unpack(inputStream, entityClass) - .collect(Collectors.toMap(UniqueEntity::getUuid, Function.identity())); + /** + * Returns a stream of {@link EntityData} that can be used to build instances of several subtypes + * of {@link Entity} by a corresponding {@link EntityFactory} that consumes this data. + * + * @param entityClass class of the entity + * @param dataSource source for the data + * @param fcn to convert {@link EntityData} to {@link E} + * @return an entity data + * @param type of entity data + */ + protected static Stream> buildEntityData( + Class entityClass, + DataSource dataSource, + Function, Try> fcn) { + return buildEntityData(entityClass, dataSource).map(fcn); } - protected static Set unpackSet( + /** + * Universal method to get a map: uuid to {@link UniqueEntity}. + * + * @param entityClass subclass of {@link UniqueEntity} + * @param dataSource source for the data + * @param factory to build the entity + * @return a map: uuid to {@link UniqueEntity} + * @param type of entity + * @throws SourceException - if an error happen during reading + */ + @SuppressWarnings("unchecked") + protected static Map getEntities( + Class entityClass, + DataSource dataSource, + EntityFactory factory) + throws SourceException { + return unpackMap( + buildEntityData(entityClass, dataSource) + .map(data -> (Try) factory.get(data)), + entityClass); + } + + /** + * Universal method to get a {@link Entity} stream. + * + * @param entityClass class of the entity + * @param dataSource source for the entity + * @param factory to build the entity + * @param fcn function to enrich the given entity data + * @return a set of {@link Entity}s + * @param type of entity + * @param type of entity data + * @throws SourceException - if an error happen during reading + */ + protected static Stream getEntities( + Class entityClass, + DataSource dataSource, + EntityFactory factory, + Function, Try> fcn) + throws SourceException { + return unpack(buildEntityData(entityClass, dataSource, fcn).map(factory::get), entityClass); + } + + protected static Map unpackMap( Stream> inputStream, Class entityClass) throws SourceException { - return unpack(inputStream, entityClass).collect(Collectors.toSet()); + return unpack(inputStream, entityClass).collect(toMap()); } protected static Stream unpack( @@ -114,4 +186,217 @@ protected static Stream unpack( .transformF(SourceException::new) .getOrThrow(); } + + /** + * Method to build an {@link Enrichment}. + * + * @param entityData data containing complex entities + * @param fieldName name of the field + * @param entities map: uuid to {@link Entity} + * @param defaultEntity entity to use if no other entity was found + * @return an enrichment with fallback value + * @param type of entity data + * @param type of entity + */ + protected static + Enrichment buildEnrichmentWithDefault( + Try entityData, + String fieldName, + Map entities, + R defaultEntity) { + return buildEnrichment(entityData, fieldName, entities).orDefault(defaultEntity); + } + + /** + * Method to build an {@link Enrichment}. + * + * @param entityData data containing complex entities + * @param fieldName name of the field + * @param entities map: uuid to {@link Entity} + * @return an enrichment + * @param type of entity data + * @param type of entity + */ + protected static Enrichment buildEnrichment( + Try entityData, String fieldName, Map entities) { + return new Enrichment<>( + fieldName, + entityData.flatMap( + data -> + Try.of(() -> data.getUUID(fieldName), FactoryException.class) + .transformF( + exception -> + new SourceException( + "Extracting UUID field " + + fieldName + + " from entity data " + + entityData + + " failed.", + exception)) + .flatMap(entityUuid -> getEntity(entityUuid, entities)))); + } + + /** + * Method to extract an {@link Entity} from a given map. + * + * @param uuid of the entity + * @param entityMap map: uuid to entity + * @return a try of the {@link Entity} + * @param type of entity + */ + protected static Try getEntity(UUID uuid, Map entityMap) { + return Optional.ofNullable(entityMap.get(uuid)) + // We either find a matching entity for given UUID, thus return a success + .map(entity -> Try.of(() -> entity, SourceException.class)) + // ... or find no matching entity, returning a failure. + .orElse( + new Failure<>(new SourceException("Entity with uuid " + uuid + " was not provided."))); + } + + /** + * Method to enrich an {@link EntityData} with an entities. Mostly used with {@link + * EnrichFunction}. + * + * @param entityData to enrich + * @param enrichment for enriching + * @param fcn to build the returned {@link EntityData} + * @return a new entity data + * @param type of entity data + * @param type of entity + * @param type of returned entity data + */ + protected static + Try enrich( + Try entityData, Enrichment enrichment, BiFunction fcn) { + return entityData.flatMap( + data -> + enrichment.map( + (fieldName, entity) -> { + Map fieldsToAttributes = data.getFieldsToValues(); + + // remove fields that are passed as objects to constructor + fieldsToAttributes.keySet().remove(fieldName); + + return fcn.apply(data, entity); + })); + } + + /** + * Method to enrich an {@link EntityData} with two entities. Mostly used with {@link + * BiEnrichFunction}. + * + * @param entityData to enrich + * @param enrichment1 first enrichment + * @param enrichment2 second enrichment + * @param fcn to build the returned {@link EntityData} + * @return a new entity data + * @param type of entity data + * @param type of first entity + * @param type of second entity + * @param type of returned entity data + */ + protected static < + E extends EntityData, T1 extends Entity, T2 extends Entity, R extends EntityData> + Try biEnrich( + Try entityData, + Enrichment enrichment1, + Enrichment enrichment2, + TriFunction fcn) { + return entityData.flatMap( + data -> + enrichment1 + .entity + .zip(enrichment2.entity) + .map( + zippedData -> { + Map fieldsToAttributes = data.getFieldsToValues(); + + // remove fields that are passed as objects to constructor + fieldsToAttributes.keySet().remove(enrichment1.fieldName); + fieldsToAttributes.keySet().remove(enrichment2.fieldName); + + return fcn.apply(data, zippedData.getKey(), zippedData.getValue()); + })); + } + + // functional interfaces + + /** + * Function for enriching an {@link EntityData} with an {@link Entity}. + * + * @param type of entity data + * @param type of entity + * @param type of returned entity data + */ + @FunctionalInterface + protected interface EnrichFunction + extends BiFunction, Map, Try> {} + + /** + * Function for enriching an {@link EntityData} with two {@link Entity}. + * + * @param type of entity data + * @param type of first entity + * @param type of second entity + * @param type of returned entity data + */ + @FunctionalInterface + protected interface BiEnrichFunction< + E extends EntityData, T1 extends Entity, T2 extends Entity, R extends EntityData> + extends TriFunction< + Try, Map, Map, Try> {} + + /** + * Function for enriching an {@link EntityData} with three {@link Entity}. + * + * @param type of entity data + * @param type of first entity + * @param type of second entity + * @param type of third entity + * @param type of returned entity data + */ + @FunctionalInterface + protected interface TriEnrichFunction< + E extends EntityData, + T1 extends Entity, + T2 extends Entity, + T3 extends Entity, + R extends EntityData> + extends QuadFunction< + Try, + Map, + Map, + Map, + Try> {} + + /** + * Container class for enriching an {@link EntityData}. + * + * @param fieldName name of the field + * @param entity try of the entity + * @param type of the entity + */ + protected record Enrichment(String fieldName, Try entity) { + + /** + * Replaces a {@link Failure} with the given entity + * + * @param defaultEntity given entity + * @return a new {@link Enrichment} + */ + public Enrichment orDefault(T defaultEntity) { + return new Enrichment<>(fieldName, entity.orElse(() -> Try.Success.of(defaultEntity))); + } + + /** + * Method to map the entity while also using the field name. + * + * @param mapper function + * @return a new {@link Try} + * @param type of entity + */ + public Try map(BiFunction mapper) { + return entity.map(data -> mapper.apply(fieldName, data)); + } + } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java index f1c82fddc..52a77d40c 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java @@ -22,8 +22,10 @@ import edu.ie3.datamodel.models.input.graphics.LineGraphicInput; import edu.ie3.datamodel.models.input.graphics.NodeGraphicInput; import edu.ie3.datamodel.utils.Try; -import edu.ie3.datamodel.utils.Try.*; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.UUID; import java.util.stream.Stream; /** @@ -118,15 +120,19 @@ public Set getNodeGraphicInput() throws SourceException { public Set getNodeGraphicInput(Map nodes) throws SourceException { - return unpackSet( - buildNodeGraphicEntityData(nodes).map(nodeGraphicInputFactory::get), - NodeGraphicInput.class); + return toSet( + getEntities( + NodeGraphicInput.class, + dataSource, + nodeGraphicInputFactory, + data -> + enrich(data, buildEnrichment(data, NODE, nodes), NodeGraphicInputEntityData::new))); } /** * If the set of {@link LineInput} entities is not exhaustive for all available {@link * LineGraphicInput} entities or if an error during the building process occurs a {@link - * SourceException} is thrown, else all entities that have been able to be built are returned. + * SourceException} is thrown, else all entities that have been able to be build are returned. */ public Set getLineGraphicInput() throws SourceException { Map operators = typeSource.getOperators(); @@ -137,63 +143,13 @@ public Set getLineGraphicInput() throws SourceException { public Set getLineGraphicInput(Map lines) throws SourceException { - return unpackSet( - buildLineGraphicEntityData(lines).map(lineGraphicInputFactory::get), - LineGraphicInput.class); - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - // build EntityData - - /** - * Builds a stream of {@link NodeGraphicInputEntityData} instances that can be consumed by a - * {@link NodeGraphicInputFactory} to build instances of {@link NodeGraphicInput} entities. This - * method depends on corresponding instances of {@link NodeInput} entities that are represented by - * a corresponding {@link NodeGraphicInput} entity. The determination of matching {@link - * NodeInput} and {@link NodeGraphicInput} entities is carried out by the UUID of the {@link - * NodeInput} entity. Hence it is crucial to only pass over collections that are pre-checked for - * the uniqueness of the UUIDs of the nodes they contain. No further sanity checks are included in - * this method. If no UUID of a {@link NodeInput} entity can be found for a {@link - * NodeGraphicInputEntityData} instance, a {@link Failure} is included in the stream and warning - * is logged. - * - * @param nodes a map of UUID to object- and uuid-unique {@link NodeInput} entities - * @return a stream of tries of {@link NodeGraphicInput} entities - */ - protected Stream> buildNodeGraphicEntityData( - Map nodes) { - return buildEntityData(NodeGraphicInput.class, dataSource) - .map( - entityDataTry -> - entityDataTry.flatMap( - entityData -> - enrichEntityData( - entityData, NODE, nodes, NodeGraphicInputEntityData::new))); - } - - /** - * Builds a stream of {@link LineGraphicInputEntityData} instances that can be consumed by a - * {@link LineGraphicInputFactory} to build instances of {@link LineGraphicInput} entities. This - * method depends on corresponding instances of {@link LineInput} entities that are represented by - * a corresponding {@link LineGraphicInput} entity. The determination of matching {@link - * LineInput} and {@link LineGraphicInput} entities is carried out by the UUID of the {@link - * LineInput} entity. Hence it is crucial to only pass over collections that are pre-checked for - * the uniqueness of the UUIDs of the nodes they contain. No further sanity checks are included in - * this method. If no UUID of a {@link LineInput} entity can be found for a {@link - * LineGraphicInputEntityData} instance, a {@link Failure} is included in the stream and warning - * is logged. - * - * @param lines a map of UUID to object- and uuid-unique {@link LineInput} entities - * @return a stream of tries of {@link LineGraphicInput} entities - */ - protected Stream> buildLineGraphicEntityData( - Map lines) { - return buildEntityData(LineGraphicInput.class, dataSource) - .map( - entityDataTry -> - entityDataTry.flatMap( - entityData -> - enrichEntityData( - entityData, "line", lines, LineGraphicInputEntityData::new))); + return toSet( + getEntities( + LineGraphicInput.class, + dataSource, + lineGraphicInputFactory, + data -> + enrich( + data, buildEnrichment(data, "line", lines), LineGraphicInputEntityData::new))); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java index 4277ff41d..b95ef6d33 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java @@ -6,6 +6,7 @@ package edu.ie3.datamodel.io.source; import edu.ie3.datamodel.exceptions.*; +import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.input.*; import edu.ie3.datamodel.models.input.*; import edu.ie3.datamodel.models.input.connector.*; @@ -14,8 +15,9 @@ import edu.ie3.datamodel.models.input.connector.type.Transformer3WTypeInput; import edu.ie3.datamodel.models.input.container.RawGridElements; import edu.ie3.datamodel.utils.Try; -import edu.ie3.datamodel.utils.Try.Failure; import java.util.*; +import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -28,11 +30,6 @@ */ public class RawGridSource extends AssetEntitySource { - // field names - protected static final String NODE_A = "nodeA"; - protected static final String NODE_B = "nodeB"; - protected static final String TYPE = "type"; - // general fields private final TypeSource typeSource; @@ -203,9 +200,12 @@ public Map getNodes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link NodeInput} entities */ public Map getNodes(Map operators) throws SourceException { - return unpackMap( - buildAssetInputEntityData(NodeInput.class, operators).map(nodeInputFactory::get), - NodeInput.class); + return getEntities( + NodeInput.class, + dataSource, + nodeInputFactory, + data -> assetEnricher.apply(data, operators)) + .collect(toMap()); } /** @@ -247,10 +247,9 @@ public Map getLines( Map nodes, Map lineTypeInputs) throws SourceException { - return unpackMap( - buildTypedEntityData(LineInput.class, operators, nodes, lineTypeInputs) - .map(lineInputFactory::get), - LineInput.class); + return getTypedConnectorEntities( + LineInput.class, dataSource, lineInputFactory, operators, nodes, lineTypeInputs) + .collect(toMap()); } /** @@ -294,10 +293,14 @@ public Set get2WTransformers( Map nodes, Map transformer2WTypes) throws SourceException { - return unpackSet( - buildTypedEntityData(Transformer2WInput.class, operators, nodes, transformer2WTypes) - .map(transformer2WInputFactory::get), - Transformer2WInput.class); + return getTypedConnectorEntities( + Transformer2WInput.class, + dataSource, + transformer2WInputFactory, + operators, + nodes, + transformer2WTypes) + .collect(Collectors.toSet()); } /** @@ -341,13 +344,22 @@ public Set get3WTransformers( Map nodes, Map transformer3WTypes) throws SourceException { - return unpackSet( - transformer3WEntityDataStream( - buildTypedEntityData( - Transformer3WInput.class, operators, nodes, transformer3WTypes), - nodes) - .map(transformer3WInputFactory::get), - Transformer3WInput.class); + + Function, Try> + builder = + data -> + connectorEnricher + .andThen( + enrichedData -> + biEnrich( + enrichedData, + buildEnrichment(enrichedData, "nodeC", nodes), + buildEnrichment(enrichedData, TYPE, transformer3WTypes), + Transformer3WInputEntityData::new)) + .apply(data, operators, nodes); + + return getEntities(Transformer3WInput.class, dataSource, transformer3WInputFactory, builder) + .collect(Collectors.toSet()); } /** @@ -385,9 +397,12 @@ public Set getSwitches() throws SourceException { */ public Set getSwitches( Map operators, Map nodes) throws SourceException { - return unpackSet( - buildUntypedEntityData(SwitchInput.class, operators, nodes).map(switchInputFactory::get), - SwitchInput.class); + return getEntities( + SwitchInput.class, + dataSource, + switchInputFactory, + data -> connectorEnricher.apply(data, operators, nodes)) + .collect(Collectors.toSet()); } /** @@ -426,103 +441,11 @@ public Set getMeasurementUnits() throws SourceException { */ public Set getMeasurementUnits( Map operators, Map nodes) throws SourceException { - return unpackSet( - buildNodeAssetEntityData(MeasurementUnitInput.class, operators, nodes) - .map(measurementUnitInputFactory::get), - MeasurementUnitInput.class); - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - /** - * Enriches the Stream of tries on {@link Transformer3WInputEntityData} with the information of - * the internal node. - * - * @param typedConnectorEntityDataStream Stream of already typed input entity data - * @param nodes Yet available nodes - * @return A stream of {@link Try} on enriched data - */ - protected Stream> - transformer3WEntityDataStream( - Stream, SourceException>> - typedConnectorEntityDataStream, - Map nodes) { - return typedConnectorEntityDataStream - .parallel() - .map( - typedEntityDataOpt -> - typedEntityDataOpt.flatMap( - typeEntityData -> - enrichEntityData( - typeEntityData, "nodeC", nodes, Transformer3WInputEntityData::new))); - } - - private - Stream, SourceException>> buildTypedEntityData( - Class entityClass, - Map operators, - Map nodes, - Map types) { - return typedConnectorEntityDataStream( - buildUntypedEntityData(entityClass, operators, nodes), types); - } - - /** - * Enriches the given untyped entity data with the equivalent asset type. If this is not possible, - * a {@link Failure} is returned. - * - * @param connectorEntityDataStream Stream of untyped entity data - * @param availableTypes Yet available asset types - * @param Type of the asset type - * @return Stream of {@link Try} to enhanced data - */ - protected - Stream, SourceException>> typedConnectorEntityDataStream( - Stream> connectorEntityDataStream, - Map availableTypes) { - return connectorEntityDataStream - .parallel() - .map( - noTypeEntityDataOpt -> - noTypeEntityDataOpt.flatMap( - noTypeEntityData -> - enrichEntityData( - noTypeEntityData, - TYPE, - availableTypes, - TypedConnectorInputEntityData::new))); - } - - public - Stream> buildUntypedEntityData( - Class entityClass, Map operators, Map nodes) { - return untypedConnectorEntityDataStream( - buildAssetInputEntityData(entityClass, operators), nodes); - } - - /** - * Converts a stream of {@link AssetInputEntityData} in connection with a collection of known - * {@link NodeInput}s to a stream of {@link ConnectorInputEntityData}. - * - * @param assetInputEntityDataStream Input stream of {@link AssetInputEntityData} - * @param nodes A collection of known nodes - * @return A stream on {@link Try} to matching {@link ConnectorInputEntityData} - */ - protected Stream> untypedConnectorEntityDataStream( - Stream> assetInputEntityDataStream, - Map nodes) { - return assetInputEntityDataStream - .parallel() - .map( - assetInputEntityDataTry -> - assetInputEntityDataTry.flatMap( - assetInputEntityData -> - enrichEntityData( - assetInputEntityData, - NODE_A, - nodes, - NODE_B, - nodes, - ConnectorInputEntityData::new))); + return getEntities( + MeasurementUnitInput.class, + dataSource, + measurementUnitInputFactory, + data -> nodeAssetEnricher.apply(data, operators, nodes)) + .collect(Collectors.toSet()); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java index 6aa36588c..30656ec88 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java @@ -8,8 +8,6 @@ import edu.ie3.datamodel.exceptions.FailedValidationException; import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.exceptions.ValidationException; -import edu.ie3.datamodel.io.factory.EntityData; -import edu.ie3.datamodel.io.factory.EntityFactory; import edu.ie3.datamodel.io.factory.result.*; import edu.ie3.datamodel.models.result.NodeResult; import edu.ie3.datamodel.models.result.ResultEntity; @@ -25,6 +23,8 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.function.Function; +import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -372,11 +372,10 @@ public Set getEmResults() throws SourceException { */ @SuppressWarnings("unchecked") private Set getResultEntities( - Class entityClass, EntityFactory factory) + Class entityClass, ResultEntityFactory factory) throws SourceException { - return unpackSet( - buildEntityData(entityClass, dataSource) - .map(entityData -> factory.get(entityData).map(data -> (T) data)), - entityClass); + return getEntities( + entityClass, dataSource, (ResultEntityFactory) factory, Function.identity()) + .collect(Collectors.toSet()); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index 1e2294cf5..ede81d525 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -9,7 +9,7 @@ import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.exceptions.SystemParticipantsException; import edu.ie3.datamodel.exceptions.ValidationException; -import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData; +import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.input.participant.*; import edu.ie3.datamodel.models.input.EmInput; import edu.ie3.datamodel.models.input.NodeInput; @@ -24,6 +24,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; +import java.util.function.Function; import java.util.stream.Stream; /** @@ -53,6 +54,21 @@ public class SystemParticipantSource extends AssetEntitySource { private final WecInputFactory wecInputFactory; private final EvcsInputFactory evcsInputFactory; + // enriching function + protected static final TriEnrichFunction< + EntityData, OperatorInput, NodeInput, EmInput, SystemParticipantEntityData> + participantEnricher = + (data, operators, nodes, emUnits) -> + nodeAssetEnricher + .andThen( + enrichedData -> + enrich( + enrichedData, + buildEnrichment( + enrichedData, SystemParticipantInputEntityFactory.EM, emUnits), + SystemParticipantEntityData::new)) + .apply(data, operators, nodes); + public SystemParticipantSource( TypeSource typeSource, ThermalSource thermalSource, @@ -267,10 +283,12 @@ public Set getFixedFeedIns() throws SourceException { public Set getFixedFeedIns( Map operators, Map nodes, Map emUnits) throws SourceException { - return unpackSet( - buildSystemParticipantEntityData(FixedFeedInInput.class, operators, nodes, emUnits) - .map(fixedFeedInInputFactory::get), - FixedFeedInInput.class); + return toSet( + getEntities( + FixedFeedInInput.class, + dataSource, + fixedFeedInInputFactory, + data -> participantEnricher.apply(data, operators, nodes, emUnits))); } /** @@ -310,10 +328,12 @@ public Set getPvPlants() throws SourceException { public Set getPvPlants( Map operators, Map nodes, Map emUnits) throws SourceException { - return unpackSet( - buildSystemParticipantEntityData(PvInput.class, operators, nodes, emUnits) - .map(pvInputFactory::get), - PvInput.class); + return toSet( + getEntities( + PvInput.class, + dataSource, + pvInputFactory, + data -> participantEnricher.apply(data, operators, nodes, emUnits))); } /** @@ -353,10 +373,12 @@ public Set getLoads() throws SourceException { public Set getLoads( Map operators, Map nodes, Map emUnits) throws SourceException { - return unpackSet( - buildSystemParticipantEntityData(LoadInput.class, operators, nodes, emUnits) - .map(loadInputFactory::get), - LoadInput.class); + return toSet( + getEntities( + LoadInput.class, + dataSource, + loadInputFactory, + data -> participantEnricher.apply(data, operators, nodes, emUnits))); } /** @@ -396,10 +418,12 @@ public Set getEvcs() throws SourceException { public Set getEvcs( Map operators, Map nodes, Map emUnits) throws SourceException { - return unpackSet( - buildSystemParticipantEntityData(EvcsInput.class, operators, nodes, emUnits) - .map(evcsInputFactory::get), - EvcsInput.class); + return toSet( + getEntities( + EvcsInput.class, + dataSource, + evcsInputFactory, + data -> participantEnricher.apply(data, operators, nodes, emUnits))); } /** @@ -444,10 +468,12 @@ public Set getBmPlants( Map emUnits, Map types) throws SourceException { - return unpackSet( - buildTypedSystemParticipantEntityData(BmInput.class, operators, nodes, emUnits, types) - .map(bmInputFactory::get), - BmInput.class); + return toSet( + getEntities( + BmInput.class, + dataSource, + bmInputFactory, + data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); } /** @@ -494,10 +520,12 @@ public Set getStorages( Map emUnits, Map types) throws SourceException { - return unpackSet( - buildTypedSystemParticipantEntityData(StorageInput.class, operators, nodes, emUnits, types) - .map(storageInputFactory::get), - StorageInput.class); + return toSet( + getEntities( + StorageInput.class, + dataSource, + storageInputFactory, + data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); } /** @@ -542,10 +570,12 @@ public Set getWecPlants( Map emUnits, Map types) throws SourceException { - return unpackSet( - buildTypedSystemParticipantEntityData(WecInput.class, operators, nodes, emUnits, types) - .map(wecInputFactory::get), - WecInput.class); + return toSet( + getEntities( + WecInput.class, + dataSource, + wecInputFactory, + data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); } /** @@ -589,10 +619,12 @@ public Set getEvs( Map emUnits, Map types) throws SourceException { - return unpackSet( - buildTypedSystemParticipantEntityData(EvInput.class, operators, nodes, emUnits, types) - .map(evInputFactory::get), - EvInput.class); + return toSet( + getEntities( + EvInput.class, + dataSource, + evInputFactory, + data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); } public Set getChpPlants() throws SourceException { @@ -635,14 +667,21 @@ public Set getChpPlants( Map thermalBuses, Map thermalStorages) throws SourceException { - return unpackSet( - chpEntityStream( - buildTypedSystemParticipantEntityData( - ChpInput.class, operators, nodes, emUnits, types), - thermalStorages, - thermalBuses) - .map(chpInputFactory::get), - ChpInput.class); + + Function, Try> builder = + data -> + participantEnricher + .andThen(participantData -> enrich(participantData, types)) + .andThen( + typedData -> + biEnrich( + typedData, + buildEnrichment(typedData, THERMAL_BUS, thermalBuses), + buildEnrichment(typedData, THERMAL_STORAGE, thermalStorages), + ChpInputEntityData::new)) + .apply(data, operators, nodes, emUnits); + + return toSet(getEntities(ChpInput.class, dataSource, chpInputFactory, builder)); } public Set getHeatPumps() throws SourceException { @@ -680,154 +719,34 @@ public Set getHeatPumps( Map types, Map thermalBuses) throws SourceException { - return unpackSet( - hpEntityStream( - buildTypedSystemParticipantEntityData( - HpInput.class, operators, nodes, emUnits, types), - thermalBuses) - .map(hpInputFactory::get), - HpInput.class); - } - - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- - - private static Stream> chpEntityStream( - Stream, SourceException>> - typedEntityDataStream, - Map thermalStorages, - Map thermalBuses) { - - return typedEntityDataStream - .parallel() - .map( - typedEntityDataOpt -> - typedEntityDataOpt.flatMap( - typedEntityData -> - enrichEntityData( - typedEntityData, - THERMAL_BUS, - thermalBuses, - THERMAL_STORAGE, - thermalStorages, - ChpInputEntityData::new))); - } - - /** - * Enriches a given stream of {@link SystemParticipantTypedEntityData} tries with a type of {@link - * ThermalBusInput} based on the provided collection of buses and the fields to values mapping - * inside the already provided {@link SystemParticipantTypedEntityData} instance. - * - * @param typedEntityDataStream the data stream of {@link SystemParticipantTypedEntityData} tries - * @param thermalBuses the thermal buses that should be used for enrichment and to build {@link - * HpInputEntityData} - * @return stream of tries of {@link HpInputEntityData} instances - */ - private static Stream> hpEntityStream( - Stream, SourceException>> - typedEntityDataStream, - Map thermalBuses) { - - return typedEntityDataStream - .parallel() - .map( - typedEntityDataOpt -> - typedEntityDataOpt.flatMap( - typedEntityData -> - enrichEntityData( - typedEntityData, THERMAL_BUS, thermalBuses, HpInputEntityData::new))); - } - - /** - * Constructs a stream of {@link SystemParticipantTypedEntityData} wrapped in {@link Try}'s. - * - * @param entityClass the class of the entities that should be built - * @param operators the operators that should be considered for these entities - * @param nodes the nodes that should be considered for these entities - * @param types the types that should be considered for these entities - * @param the type of the type model of the resulting entity - * @return a stream of tries holding an instance of a {@link SystemParticipantTypedEntityData} - */ - private - Stream, SourceException>> - buildTypedSystemParticipantEntityData( - Class entityClass, - Map operators, - Map nodes, - Map emUnits, - Map types) { - return typedSystemParticipantEntityStream( - buildSystemParticipantEntityData(entityClass, operators, nodes, emUnits), types); - } - /** - * Enriches a given stream of {@link SystemParticipantEntityData} {@link Try} objects with a type - * of {@link SystemParticipantTypeInput} based on the provided collection of types and the fields - * to values mapping that inside the already provided {@link SystemParticipantEntityData} - * instance. - * - * @param systemParticipantEntityDataStream the data stream of {@link SystemParticipantEntityData} - * {@link Try} objects - * @param types the types that should be used for enrichment and to build {@link - * SystemParticipantTypedEntityData} from - * @param the type of the provided entity types as well as the type parameter of the resulting - * {@link SystemParticipantTypedEntityData} - * @return a stream of tries of {@link SystemParticipantTypedEntityData} instances - */ - private static - Stream, SourceException>> - typedSystemParticipantEntityStream( - Stream> - systemParticipantEntityDataStream, - Map types) { - return systemParticipantEntityDataStream - .parallel() - .map( - participantEntityDataTry -> - participantEntityDataTry.flatMap( - participantEntityData -> - enrichEntityData( - participantEntityData, - TYPE, - types, - SystemParticipantTypedEntityData::new))); + Function, Try> builder = + data -> + participantEnricher + .andThen(participantData -> enrich(participantData, types)) + .andThen( + typedData -> + enrich( + typedData, + buildEnrichment(typedData, THERMAL_BUS, thermalBuses), + HpInputEntityData::new)) + .apply(data, operators, nodes, emUnits); + return toSet(getEntities(HpInput.class, dataSource, hpInputFactory, builder)); } - private Stream> - buildSystemParticipantEntityData( - Class entityClass, - Map operators, - Map nodes, - Map emUnits) { - return systemParticipantEntityStream( - buildNodeAssetEntityData(entityClass, operators, nodes), emUnits); - } + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /** - * Enriches a given stream of {@link NodeAssetInputEntityData} {@link Try} objects with a type of - * {@link EmInput} based on the provided collection of EMs and the fields to values mapping that - * inside the already provided {@link NodeAssetInputEntityData} instance. - * - * @param nodeAssetEntityDataStream the data stream of {@link NodeAssetInputEntityData} {@link - * Try} objects - * @param emUnits the energy management units that should be used for enrichment and to build - * {@link SystemParticipantEntityData} from - * @return a stream of tries of {@link SystemParticipantEntityData} instances + * Method for enriching {@link SystemParticipantEntityData} with types. + * + * @param data to enrich + * @param types all known types + * @return a typed entity data + * @param type of types */ - private static Stream> - systemParticipantEntityStream( - Stream> nodeAssetEntityDataStream, - Map emUnits) { - return nodeAssetEntityDataStream - .parallel() - .map( - nodeAssetInputEntityDataTry -> - nodeAssetInputEntityDataTry.flatMap( - nodeAssetInputEntityData -> - optionallyEnrichEntityData( - nodeAssetInputEntityData, - SystemParticipantInputEntityFactory.EM, - emUnits, - null, - SystemParticipantEntityData::new))); + private static + Try, SourceException> enrich( + Try data, Map types) { + return enrich(data, buildEnrichment(data, TYPE, types), SystemParticipantTypedEntityData::new); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java index 3d078f70b..c9c76d274 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java @@ -6,8 +6,8 @@ package edu.ie3.datamodel.io.source; import edu.ie3.datamodel.exceptions.*; +import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.input.*; -import edu.ie3.datamodel.models.UniqueEntity; import edu.ie3.datamodel.models.input.OperatorInput; import edu.ie3.datamodel.models.input.thermal.CylindricalStorageInput; import edu.ie3.datamodel.models.input.thermal.ThermalBusInput; @@ -17,8 +17,6 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -38,6 +36,20 @@ public class ThermalSource extends AssetEntitySource { private final CylindricalStorageInputFactory cylindricalStorageInputFactory; private final ThermalHouseInputFactory thermalHouseInputFactory; + // enriching function + protected static BiEnrichFunction< + EntityData, OperatorInput, ThermalBusInput, ThermalUnitInputEntityData> + thermalUnitEnricher = + (data, operators, buses) -> + assetEnricher + .andThen( + assetData -> + enrich( + assetData, + buildEnrichment(assetData, "thermalbus", buses), + ThermalUnitInputEntityData::new)) + .apply(data, operators); + public ThermalSource(TypeSource typeSource, DataSource dataSource) { super(dataSource); this.typeSource = typeSource; @@ -94,10 +106,12 @@ public Map getThermalBuses() throws SourceException { */ public Map getThermalBuses(Map operators) throws SourceException { - return unpackMap( - buildAssetInputEntityData(ThermalBusInput.class, operators) - .map(thermalBusInputFactory::get), - ThermalBusInput.class); + return getEntities( + ThermalBusInput.class, + dataSource, + thermalBusInputFactory, + data -> assetEnricher.apply(data, operators)) + .collect(toMap()); } /** @@ -112,8 +126,7 @@ public Map getThermalBuses(Map opera * @return a map of UUID to object- and uuid-unique {@link ThermalStorageInput} entities */ public Map getThermalStorages() throws SourceException { - return getCylindricalStorages().stream() - .collect(Collectors.toMap(UniqueEntity::getUuid, Function.identity())); + return getCylindricalStorages().stream().collect(toMap()); } /** @@ -138,8 +151,7 @@ public Map getThermalStorages() throws SourceExceptio public Map getThermalStorages( Map operators, Map thermalBuses) throws SourceException { - return getCylindricalStorages(operators, thermalBuses).stream() - .collect(Collectors.toMap(UniqueEntity::getUuid, Function.identity())); + return getCylindricalStorages(operators, thermalBuses).stream().collect(toMap()); } /** @@ -182,11 +194,12 @@ public Map getThermalHouses() throws SourceException { public Map getThermalHouses( Map operators, Map thermalBuses) throws SourceException { - return unpackMap( - thermalUnitInputEntityDataStream( - buildAssetInputEntityData(ThermalHouseInput.class, operators), thermalBuses) - .map(thermalHouseInputFactory::get), - ThermalHouseInput.class); + return getEntities( + ThermalHouseInput.class, + dataSource, + thermalHouseInputFactory, + data -> thermalUnitEnricher.apply(data, operators, thermalBuses)) + .collect(toMap()); } /** @@ -229,38 +242,11 @@ public Set getCylindricalStorages() throws SourceExcept public Set getCylindricalStorages( Map operators, Map thermalBuses) throws SourceException { - return unpackSet( - thermalUnitInputEntityDataStream( - buildAssetInputEntityData(CylindricalStorageInput.class, operators), thermalBuses) - .map(cylindricalStorageInputFactory::get), - CylindricalStorageInput.class); - } - - /** - * Enriches a given stream of {@link AssetInputEntityData} {@link Try} objects with a type of - * {@link ThermalBusInput} based on the provided collection of types and the fields to values - * mapping that inside the already provided {@link AssetInputEntityData} instance. - * - * @param assetInputEntityDataStream the data stream of {@link AssetInputEntityData} {@link Try} - * objects - * @param thermalBuses the thermal buses that should be used for enrichment and to build {@link - * ThermalUnitInputEntityData} from - * @return a stream of tries of {@link ThermalUnitInputEntityData} instances - */ - private static Stream> - thermalUnitInputEntityDataStream( - Stream> assetInputEntityDataStream, - Map thermalBuses) { - return assetInputEntityDataStream - .parallel() - .map( - assetInputEntityDataTry -> - assetInputEntityDataTry.flatMap( - assetInputEntityData -> - enrichEntityData( - assetInputEntityData, - "thermalbus", - thermalBuses, - ThermalUnitInputEntityData::new))); + return toSet( + getEntities( + CylindricalStorageInput.class, + dataSource, + cylindricalStorageInputFactory, + data -> thermalUnitEnricher.apply(data, operators, thermalBuses))); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java b/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java index 35e3edd29..16b2a9f4f 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/TypeSource.java @@ -5,20 +5,15 @@ */ package edu.ie3.datamodel.io.source; -import edu.ie3.datamodel.exceptions.FactoryException; import edu.ie3.datamodel.exceptions.FailedValidationException; import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.exceptions.ValidationException; -import edu.ie3.datamodel.io.factory.EntityData; -import edu.ie3.datamodel.io.factory.EntityFactory; import edu.ie3.datamodel.io.factory.input.OperatorInputFactory; import edu.ie3.datamodel.io.factory.typeinput.LineTypeInputFactory; import edu.ie3.datamodel.io.factory.typeinput.SystemParticipantTypeInputFactory; import edu.ie3.datamodel.io.factory.typeinput.Transformer2WTypeInputFactory; import edu.ie3.datamodel.io.factory.typeinput.Transformer3WTypeInputFactory; -import edu.ie3.datamodel.models.input.AssetTypeInput; import edu.ie3.datamodel.models.input.OperatorInput; -import edu.ie3.datamodel.models.input.UniqueInputEntity; import edu.ie3.datamodel.models.input.connector.type.LineTypeInput; import edu.ie3.datamodel.models.input.connector.type.Transformer2WTypeInput; import edu.ie3.datamodel.models.input.connector.type.Transformer3WTypeInput; @@ -95,10 +90,7 @@ public void validate() throws ValidationException { * @return a map of UUID to object- and uuid-unique {@link Transformer2WTypeInput} entities */ public Map getTransformer2WTypes() throws SourceException { - return unpackMap( - buildEntityData(Transformer2WTypeInput.class, dataSource) - .map(transformer2WTypeInputFactory::get), - Transformer2WTypeInput.class); + return getEntities(Transformer2WTypeInput.class, dataSource, transformer2WTypeInputFactory); } /** @@ -111,9 +103,7 @@ public Map getTransformer2WTypes() throws SourceEx * @return a map of UUID to object- and uuid-unique {@link OperatorInput} entities */ public Map getOperators() throws SourceException { - return unpackMap( - buildEntityData(OperatorInput.class, dataSource).map(operatorInputFactory::get), - OperatorInput.class); + return getEntities(OperatorInput.class, dataSource, operatorInputFactory); } /** @@ -126,9 +116,7 @@ public Map getOperators() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link LineTypeInput} entities */ public Map getLineTypes() throws SourceException { - return unpackMap( - buildEntityData(LineTypeInput.class, dataSource).map(lineTypeInputFactory::get), - LineTypeInput.class); + return getEntities(LineTypeInput.class, dataSource, lineTypeInputFactory); } /** @@ -142,10 +130,7 @@ public Map getLineTypes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link Transformer3WTypeInput} entities */ public Map getTransformer3WTypes() throws SourceException { - return unpackMap( - buildEntityData(Transformer3WTypeInput.class, dataSource) - .map(transformer3WTypeInputFactory::get), - Transformer3WTypeInput.class); + return getEntities(Transformer3WTypeInput.class, dataSource, transformer3WTypeInputFactory); } /** @@ -158,8 +143,7 @@ public Map getTransformer3WTypes() throws SourceEx * @return a map of UUID to object- and uuid-unique {@link BmTypeInput} entities */ public Map getBmTypes() throws SourceException { - return unpackMap( - buildEntities(BmTypeInput.class, systemParticipantTypeInputFactory), BmTypeInput.class); + return getEntities(BmTypeInput.class, dataSource, systemParticipantTypeInputFactory); } /** @@ -172,8 +156,7 @@ public Map getBmTypes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link ChpTypeInput} entities */ public Map getChpTypes() throws SourceException { - return unpackMap( - buildEntities(ChpTypeInput.class, systemParticipantTypeInputFactory), ChpTypeInput.class); + return getEntities(ChpTypeInput.class, dataSource, systemParticipantTypeInputFactory); } /** @@ -186,8 +169,7 @@ public Map getChpTypes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link HpTypeInput} entities */ public Map getHpTypes() throws SourceException { - return unpackMap( - buildEntities(HpTypeInput.class, systemParticipantTypeInputFactory), HpTypeInput.class); + return getEntities(HpTypeInput.class, dataSource, systemParticipantTypeInputFactory); } /** @@ -201,9 +183,7 @@ public Map getHpTypes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link StorageTypeInput} entities */ public Map getStorageTypes() throws SourceException { - return unpackMap( - buildEntities(StorageTypeInput.class, systemParticipantTypeInputFactory), - StorageTypeInput.class); + return getEntities(StorageTypeInput.class, dataSource, systemParticipantTypeInputFactory); } /** @@ -216,8 +196,7 @@ public Map getStorageTypes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link WecTypeInput} entities */ public Map getWecTypes() throws SourceException { - return unpackMap( - buildEntities(WecTypeInput.class, systemParticipantTypeInputFactory), WecTypeInput.class); + return getEntities(WecTypeInput.class, dataSource, systemParticipantTypeInputFactory); } /** @@ -230,23 +209,6 @@ public Map getWecTypes() throws SourceException { * @return a map of UUID to object- and uuid-unique {@link EvTypeInput} entities */ public Map getEvTypes() throws SourceException { - return unpackMap( - buildEntities(EvTypeInput.class, systemParticipantTypeInputFactory), EvTypeInput.class); - } - - /** - * Build and cast entities to the correct type, since {@link SystemParticipantTypeInputFactory} - * outputs {@link SystemParticipantTypeInput} of general type. - * - * @param entityClass The class of type input entity to build - * @param factory The factory to use to build the entity - * @return a stream of {@link Try}s containing the desired entities - * @param The type of AssetType to return - */ - @SuppressWarnings("unchecked") - private Stream> buildEntities( - Class entityClass, EntityFactory factory) { - return buildEntityData(entityClass, dataSource) - .map(data -> (Try) factory.get(data)); + return getEntities(EvTypeInput.class, dataSource, systemParticipantTypeInputFactory); } } diff --git a/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java b/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java new file mode 100644 index 000000000..f26b409d3 --- /dev/null +++ b/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java @@ -0,0 +1,50 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation +*/ +package edu.ie3.datamodel.utils; + +import java.util.Objects; +import java.util.function.Function; + +/** + * Enhancement of {@link Function} and {@link java.util.function.BiFunction} that accepts three + * arguments and produces a result. + * + * @param the type of the first argument to the function + * @param the type of the second argument to the function + * @param the type of the third argument to the function + * @param the type of the fourth argument to the function + * @param the type of the result of the function + */ +@FunctionalInterface +public interface QuadFunction { + + /** + * Applies this function to the given arguments. + * + * @param a the first function argument + * @param b the second function argument + * @param c the third function argument + * @param d the fourth function argument + * @return the function result + */ + R apply(A a, B b, C c, D d); + + /** + * Returns a composed function that first applies this function to its input, and then applies the + * {@code after} function to the result. If evaluation of either function throws an exception, it + * is relayed to the caller of the composed function. + * + * @param the type of output of the {@code after} function, and of the composed function + * @param after the function to apply after this function is applied + * @return a composed function that first applies this function and then applies the {@code after} + * function + * @throws NullPointerException if after is null + */ + default QuadFunction andThen(Function after) { + Objects.requireNonNull(after); + return (final A a, final B b, final C c, final D d) -> after.apply(apply(a, b, c, d)); + } +} diff --git a/src/main/java/edu/ie3/datamodel/utils/Try.java b/src/main/java/edu/ie3/datamodel/utils/Try.java index 1b8da0f6b..5e573c837 100644 --- a/src/main/java/edu/ie3/datamodel/utils/Try.java +++ b/src/main/java/edu/ie3/datamodel/utils/Try.java @@ -10,10 +10,12 @@ import edu.ie3.datamodel.exceptions.FailureException; import edu.ie3.datamodel.exceptions.TryException; import java.util.*; +import java.util.function.BiFunction; import java.util.function.Function; import java.util.function.Supplier; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.tuple.Pair; public abstract class Try { // static utility methods @@ -271,6 +273,28 @@ public abstract Try transformF( public abstract Try transform( Function successFunc, Function failureFunc); + /** + * Method for zipping two tries. + * + * @param that other try + * @return a try of a pair. + * @param type of others data + */ + public Try, E> zip(Try that) { + return zip(that, Pair::of); + } + + /** + * Method for zipping two tries. + * + * @param that other try + * @param zipper function for zipping the two data types + * @return a try of type {@link P}. + * @param type of others data + * @param

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 literal 63721 zcmb5Wb9gP!wgnp7wrv|bwr$&XvSZt}Z6`anZSUAlc9NHKf9JdJ;NJVr`=eI(_pMp0 zy1VAAG3FfAOI`{X1O)&90s;U4K;XLp008~hCjbEC_fbYfS%6kTR+JtXK>nW$ZR+`W ze|#J8f4A@M|F5BpfUJb5h>|j$jOe}0oE!`Zf6fM>CR?!y@zU(cL8NsKk`a z6tx5mAkdjD;J=LcJ;;Aw8p!v#ouk>mUDZF@ zK>yvw%+bKu+T{Nk@LZ;zkYy0HBKw06_IWcMHo*0HKpTsEFZhn5qCHH9j z)|XpN&{`!0a>Vl+PmdQc)Yg4A(AG-z!+@Q#eHr&g<9D?7E)_aEB?s_rx>UE9TUq|? z;(ggJt>9l?C|zoO@5)tu?EV0x_7T17q4fF-q3{yZ^ipUbKcRZ4Qftd!xO(#UGhb2y>?*@{xq%`(-`2T^vc=#< zx!+@4pRdk&*1ht2OWk^Z5IAQ0YTAXLkL{(D*$gENaD)7A%^XXrCchN&z2x+*>o2FwPFjWpeaL=!tzv#JOW#( z$B)Nel<+$bkH1KZv3&-}=SiG~w2sbDbAWarg%5>YbC|}*d9hBjBkR(@tyM0T)FO$# zPtRXukGPnOd)~z=?avu+4Co@wF}1T)-uh5jI<1$HLtyDrVak{gw`mcH@Q-@wg{v^c zRzu}hMKFHV<8w}o*yg6p@Sq%=gkd~;`_VGTS?L@yVu`xuGy+dH6YOwcP6ZE`_0rK% zAx5!FjDuss`FQ3eF|mhrWkjux(Pny^k$u_)dyCSEbAsecHsq#8B3n3kDU(zW5yE|( zgc>sFQywFj5}U*qtF9Y(bi*;>B7WJykcAXF86@)z|0-Vm@jt!EPoLA6>r)?@DIobIZ5Sx zsc@OC{b|3%vaMbyeM|O^UxEYlEMHK4r)V-{r)_yz`w1*xV0|lh-LQOP`OP`Pk1aW( z8DSlGN>Ts|n*xj+%If~+E_BxK)~5T#w6Q1WEKt{!Xtbd`J;`2a>8boRo;7u2M&iOop4qcy<)z023=oghSFV zST;?S;ye+dRQe>ygiJ6HCv4;~3DHtJ({fWeE~$H@mKn@Oh6Z(_sO>01JwH5oA4nvK zr5Sr^g+LC zLt(i&ecdmqsIJGNOSUyUpglvhhrY8lGkzO=0USEKNL%8zHshS>Qziu|`eyWP^5xL4 zRP122_dCJl>hZc~?58w~>`P_s18VoU|7(|Eit0-lZRgLTZKNq5{k zE?V=`7=R&ro(X%LTS*f+#H-mGo_j3dm@F_krAYegDLk6UV{`UKE;{YSsn$ z(yz{v1@p|p!0>g04!eRSrSVb>MQYPr8_MA|MpoGzqyd*$@4j|)cD_%^Hrd>SorF>@ zBX+V<@vEB5PRLGR(uP9&U&5=(HVc?6B58NJT_igiAH*q~Wb`dDZpJSKfy5#Aag4IX zj~uv74EQ_Q_1qaXWI!7Vf@ZrdUhZFE;L&P_Xr8l@GMkhc#=plV0+g(ki>+7fO%?Jb zl+bTy7q{w^pTb{>(Xf2q1BVdq?#f=!geqssXp z4pMu*q;iiHmA*IjOj4`4S&|8@gSw*^{|PT}Aw~}ZXU`6=vZB=GGeMm}V6W46|pU&58~P+?LUs%n@J}CSrICkeng6YJ^M? zS(W?K4nOtoBe4tvBXs@@`i?4G$S2W&;$z8VBSM;Mn9 zxcaEiQ9=vS|bIJ>*tf9AH~m&U%2+Dim<)E=}KORp+cZ^!@wI`h1NVBXu{@%hB2Cq(dXx_aQ9x3mr*fwL5!ZryQqi|KFJuzvP zK1)nrKZ7U+B{1ZmJub?4)Ln^J6k!i0t~VO#=q1{?T)%OV?MN}k5M{}vjyZu#M0_*u z8jwZKJ#Df~1jcLXZL7bnCEhB6IzQZ-GcoQJ!16I*39iazoVGugcKA{lhiHg4Ta2fD zk1Utyc5%QzZ$s3;p0N+N8VX{sd!~l*Ta3|t>lhI&G`sr6L~G5Lul`>m z{!^INm?J|&7X=;{XveF!(b*=?9NAp4y&r&N3(GKcW4rS(Ejk|Lzs1PrxPI_owB-`H zg3(Rruh^&)`TKA6+_!n>RdI6pw>Vt1_j&+bKIaMTYLiqhZ#y_=J8`TK{Jd<7l9&sY z^^`hmi7^14s16B6)1O;vJWOF$=$B5ONW;;2&|pUvJlmeUS&F;DbSHCrEb0QBDR|my zIs+pE0Y^`qJTyH-_mP=)Y+u^LHcuZhsM3+P||?+W#V!_6E-8boP#R-*na4!o-Q1 zVthtYhK{mDhF(&7Okzo9dTi03X(AE{8cH$JIg%MEQca`S zy@8{Fjft~~BdzWC(di#X{ny;!yYGK9b@=b|zcKZ{vv4D8i+`ilOPl;PJl{!&5-0!w z^fOl#|}vVg%=n)@_e1BrP)`A zKPgs`O0EO}Y2KWLuo`iGaKu1k#YR6BMySxQf2V++Wo{6EHmK>A~Q5o73yM z-RbxC7Qdh0Cz!nG+7BRZE>~FLI-?&W_rJUl-8FDIaXoNBL)@1hwKa^wOr1($*5h~T zF;%f^%<$p8Y_yu(JEg=c_O!aZ#)Gjh$n(hfJAp$C2he555W5zdrBqjFmo|VY+el;o z=*D_w|GXG|p0**hQ7~9-n|y5k%B}TAF0iarDM!q-jYbR^us(>&y;n^2l0C%@2B}KM zyeRT9)oMt97Agvc4sEKUEy%MpXr2vz*lb zh*L}}iG>-pqDRw7ud{=FvTD?}xjD)w{`KzjNom-$jS^;iw0+7nXSnt1R@G|VqoRhE%12nm+PH?9`(4rM0kfrZzIK9JU=^$YNyLvAIoxl#Q)xxDz!^0@zZ zSCs$nfcxK_vRYM34O<1}QHZ|hp4`ioX3x8(UV(FU$J@o%tw3t4k1QPmlEpZa2IujG&(roX_q*%e`Hq|);0;@k z0z=fZiFckp#JzW0p+2A+D$PC~IsakhJJkG(c;CqAgFfU0Z`u$PzG~-9I1oPHrCw&)@s^Dc~^)#HPW0Ra}J^=|h7Fs*<8|b13ZzG6MP*Q1dkoZ6&A^!}|hbjM{2HpqlSXv_UUg1U4gn z3Q)2VjU^ti1myodv+tjhSZp%D978m~p& z43uZUrraHs80Mq&vcetqfQpQP?m!CFj)44t8Z}k`E798wxg&~aCm+DBoI+nKq}&j^ zlPY3W$)K;KtEajks1`G?-@me7C>{PiiBu+41#yU_c(dITaqE?IQ(DBu+c^Ux!>pCj zLC|HJGU*v+!it1(;3e`6igkH(VA)-S+k(*yqxMgUah3$@C zz`7hEM47xr>j8^g`%*f=6S5n>z%Bt_Fg{Tvmr+MIsCx=0gsu_sF`q2hlkEmisz#Fy zj_0;zUWr;Gz}$BS%Y`meb(=$d%@Crs(OoJ|}m#<7=-A~PQbyN$x%2iXP2@e*nO0b7AwfH8cCUa*Wfu@b)D_>I*%uE4O3 z(lfnB`-Xf*LfC)E}e?%X2kK7DItK6Tf<+M^mX0Ijf_!IP>7c8IZX%8_#0060P{QMuV^B9i<^E`_Qf0pv9(P%_s8D`qvDE9LK9u-jB}J2S`(mCO&XHTS04Z5Ez*vl^T%!^$~EH8M-UdwhegL>3IQ*)(MtuH2Xt1p!fS4o~*rR?WLxlA!sjc2(O znjJn~wQ!Fp9s2e^IWP1C<4%sFF}T4omr}7+4asciyo3DntTgWIzhQpQirM$9{EbQd z3jz9vS@{aOqTQHI|l#aUV@2Q^Wko4T0T04Me4!2nsdrA8QY1%fnAYb~d2GDz@lAtfcHq(P7 zaMBAGo}+NcE-K*@9y;Vt3*(aCaMKXBB*BJcD_Qnxpt75r?GeAQ}*|>pYJE=uZb73 zC>sv)18)q#EGrTG6io*}JLuB_jP3AU1Uiu$D7r|2_zlIGb9 zjhst#ni)Y`$)!fc#reM*$~iaYoz~_Cy7J3ZTiPm)E?%`fbk`3Tu-F#`{i!l5pNEn5 zO-Tw-=TojYhzT{J=?SZj=Z8#|eoF>434b-DXiUsignxXNaR3 zm_}4iWU$gt2Mw5NvZ5(VpF`?X*f2UZDs1TEa1oZCif?Jdgr{>O~7}-$|BZ7I(IKW`{f;@|IZFX*R8&iT= zoWstN8&R;}@2Ka%d3vrLtR|O??ben;k8QbS-WB0VgiCz;<$pBmIZdN!aalyCSEm)crpS9dcD^Y@XT1a3+zpi-`D}e#HV<} z$Y(G&o~PvL-xSVD5D?JqF3?B9rxGWeb=oEGJ3vRp5xfBPlngh1O$yI95EL+T8{GC@ z98i1H9KhZGFl|;`)_=QpM6H?eDPpw~^(aFQWwyXZ8_EEE4#@QeT_URray*mEOGsGc z6|sdXtq!hVZo=d#+9^@lm&L5|q&-GDCyUx#YQiccq;spOBe3V+VKdjJA=IL=Zn%P} zNk=_8u}VhzFf{UYZV0`lUwcD&)9AFx0@Fc6LD9A6Rd1=ga>Mi0)_QxM2ddCVRmZ0d z+J=uXc(?5JLX3=)e)Jm$HS2yF`44IKhwRnm2*669_J=2LlwuF5$1tAo@ROSU@-y+;Foy2IEl2^V1N;fk~YR z?&EP8#t&m0B=?aJeuz~lHjAzRBX>&x=A;gIvb>MD{XEV zV%l-+9N-)i;YH%nKP?>f`=?#`>B(`*t`aiPLoQM(a6(qs4p5KFjDBN?8JGrf3z8>= zi7sD)c)Nm~x{e<^jy4nTx${P~cwz_*a>%0_;ULou3kHCAD7EYkw@l$8TN#LO9jC( z1BeFW`k+bu5e8Ns^a8dPcjEVHM;r6UX+cN=Uy7HU)j-myRU0wHd$A1fNI~`4;I~`zC)3ul#8#^rXVSO*m}Ag>c%_;nj=Nv$rCZ z*~L@C@OZg%Q^m)lc-kcX&a*a5`y&DaRxh6O*dfhLfF+fU5wKs(1v*!TkZidw*)YBP za@r`3+^IHRFeO%!ai%rxy;R;;V^Fr=OJlpBX;(b*3+SIw}7= zIq$*Thr(Zft-RlY)D3e8V;BmD&HOfX+E$H#Y@B3?UL5L~_fA-@*IB-!gItK7PIgG9 zgWuGZK_nuZjHVT_Fv(XxtU%)58;W39vzTI2n&)&4Dmq7&JX6G>XFaAR{7_3QB6zsT z?$L8c*WdN~nZGiscY%5KljQARN;`w$gho=p006z;n(qIQ*Zu<``TMO3n0{ARL@gYh zoRwS*|Niw~cR!?hE{m*y@F`1)vx-JRfqET=dJ5_(076st(=lFfjtKHoYg`k3oNmo_ zNbQEw8&sO5jAYmkD|Zaz_yUb0rC})U!rCHOl}JhbYIDLzLvrZVw0~JO`d*6f;X&?V=#T@ND*cv^I;`sFeq4 z##H5;gpZTb^0Hz@3C*~u0AqqNZ-r%rN3KD~%Gw`0XsIq$(^MEb<~H(2*5G^<2(*aI z%7}WB+TRlMIrEK#s0 z93xn*Ohb=kWFc)BNHG4I(~RPn-R8#0lqyBBz5OM6o5|>x9LK@%HaM}}Y5goCQRt2C z{j*2TtT4ne!Z}vh89mjwiSXG=%DURar~=kGNNaO_+Nkb+tRi~Rkf!7a$*QlavziD( z83s4GmQ^Wf*0Bd04f#0HX@ua_d8 z23~z*53ePD6@xwZ(vdl0DLc=>cPIOPOdca&MyR^jhhKrdQO?_jJh`xV3GKz&2lvP8 zEOwW6L*ufvK;TN{=S&R@pzV^U=QNk^Ec}5H z+2~JvEVA{`uMAr)?Kf|aW>33`)UL@bnfIUQc~L;TsTQ6>r-<^rB8uoNOJ>HWgqMI8 zSW}pZmp_;z_2O5_RD|fGyTxaxk53Hg_3Khc<8AUzV|ZeK{fp|Ne933=1&_^Dbv5^u zB9n=*)k*tjHDRJ@$bp9mrh}qFn*s}npMl5BMDC%Hs0M0g-hW~P*3CNG06G!MOPEQ_ zi}Qs-6M8aMt;sL$vlmVBR^+Ry<64jrm1EI1%#j?c?4b*7>)a{aDw#TfTYKq+SjEFA z(aJ&z_0?0JB83D-i3Vh+o|XV4UP+YJ$9Boid2^M2en@APw&wx7vU~t$r2V`F|7Qfo z>WKgI@eNBZ-+Og<{u2ZiG%>YvH2L3fNpV9J;WLJoBZda)01Rn;o@){01{7E#ke(7U zHK>S#qZ(N=aoae*4X!0A{)nu0R_sKpi1{)u>GVjC+b5Jyl6#AoQ-1_3UDovNSo`T> z?c-@7XX*2GMy?k?{g)7?Sv;SJkmxYPJPs!&QqB12ejq`Lee^-cDveVWL^CTUldb(G zjDGe(O4P=S{4fF=#~oAu>LG>wrU^z_?3yt24FOx>}{^lCGh8?vtvY$^hbZ)9I0E3r3NOlb9I?F-Yc=r$*~l`4N^xzlV~N zl~#oc>U)Yjl0BxV>O*Kr@lKT{Z09OXt2GlvE38nfs+DD7exl|&vT;)>VFXJVZp9Np zDK}aO;R3~ag$X*|hRVY3OPax|PG`@_ESc8E!mHRByJbZQRS38V2F__7MW~sgh!a>98Q2%lUNFO=^xU52|?D=IK#QjwBky-C>zOWlsiiM&1n z;!&1((Xn1$9K}xabq~222gYvx3hnZPg}VMF_GV~5ocE=-v>V=T&RsLBo&`)DOyIj* zLV{h)JU_y*7SdRtDajP_Y+rBkNN*1_TXiKwHH2&p51d(#zv~s#HwbNy?<+(=9WBvo zw2hkk2Dj%kTFhY+$T+W-b7@qD!bkfN#Z2ng@Pd=i3-i?xYfs5Z*1hO?kd7Sp^9`;Y zM2jeGg<-nJD1er@Pc_cSY7wo5dzQX44=%6rn}P_SRbpzsA{6B+!$3B0#;}qwO37G^ zL(V_5JK`XT?OHVk|{_$vQ|oNEpab*BO4F zUTNQ7RUhnRsU`TK#~`)$icsvKh~(pl=3p6m98@k3P#~upd=k*u20SNcb{l^1rUa)>qO997)pYRWMncC8A&&MHlbW?7i^7M`+B$hH~Y|J zd>FYOGQ;j>Zc2e7R{KK7)0>>nn_jYJy&o@sK!4G>-rLKM8Hv)f;hi1D2fAc$+six2 zyVZ@wZ6x|fJ!4KrpCJY=!Mq0;)X)OoS~{Lkh6u8J`eK%u0WtKh6B>GW_)PVc zl}-k`p09qwGtZ@VbYJC!>29V?Dr>>vk?)o(x?!z*9DJ||9qG-&G~#kXxbw{KKYy}J zQKa-dPt~M~E}V?PhW0R26xdA%1T*%ra6SguGu50YHngOTIv)@N|YttEXo#OZfgtP7;H?EeZZxo<}3YlYxtBq znJ!WFR^tmGf0Py}N?kZ(#=VtpC@%xJkDmfcCoBTxq zr_|5gP?u1@vJZbxPZ|G0AW4=tpb84gM2DpJU||(b8kMOV1S3|(yuwZJ&rIiFW(U;5 zUtAW`O6F6Zy+eZ1EDuP~AAHlSY-+A_eI5Gx)%*uro5tljy}kCZU*_d7)oJ>oQSZ3* zneTn`{gnNC&uJd)0aMBzAg021?YJ~b(fmkwZAd696a=0NzBAqBN54KuNDwa*no(^O z6p05bioXUR^uXjpTol*ppHp%1v9e)vkoUAUJyBx3lw0UO39b0?^{}yb!$yca(@DUn zCquRF?t=Zb9`Ed3AI6|L{eX~ijVH`VzSMheKoP7LSSf4g>md>`yi!TkoG5P>Ofp+n z(v~rW+(5L96L{vBb^g51B=(o)?%%xhvT*A5btOpw(TKh^g^4c zw>0%X!_0`{iN%RbVk+A^f{w-4-SSf*fu@FhruNL##F~sF24O~u zyYF<3el2b$$wZ_|uW#@Ak+VAGk#e|kS8nL1g>2B-SNMjMp^8;-FfeofY2fphFHO!{ z*!o4oTb{4e;S<|JEs<1_hPsmAlVNk?_5-Fp5KKU&d#FiNW~Y+pVFk@Cua1I{T+1|+ zHx6rFMor)7L)krbilqsWwy@T+g3DiH5MyVf8Wy}XbEaoFIDr~y;@r&I>FMW{ z?Q+(IgyebZ)-i4jNoXQhq4Muy9Fv+OxU;9_Jmn+<`mEC#%2Q_2bpcgzcinygNI!&^ z=V$)o2&Yz04~+&pPWWn`rrWxJ&}8khR)6B(--!9Q zubo}h+1T)>a@c)H^i``@<^j?|r4*{;tQf78(xn0g39IoZw0(CwY1f<%F>kEaJ zp9u|IeMY5mRdAlw*+gSN^5$Q)ShM<~E=(c8QM+T-Qk)FyKz#Sw0EJ*edYcuOtO#~Cx^(M7w5 z3)rl#L)rF|(Vun2LkFr!rg8Q@=r>9p>(t3Gf_auiJ2Xx9HmxYTa|=MH_SUlYL`mz9 zTTS$`%;D-|Jt}AP1&k7PcnfFNTH0A-*FmxstjBDiZX?}%u%Yq94$fUT&z6od+(Uk> zuqsld#G(b$G8tus=M!N#oPd|PVFX)?M?tCD0tS%2IGTfh}3YA3f&UM)W$_GNV8 zQo+a(ml2Km4o6O%gKTCSDNq+#zCTIQ1*`TIJh~k6Gp;htHBFnne))rlFdGqwC6dx2+La1&Mnko*352k0y z+tQcwndQlX`nc6nb$A9?<-o|r*%aWXV#=6PQic0Ok_D;q>wbv&j7cKc!w4~KF#-{6 z(S%6Za)WpGIWf7jZ3svNG5OLs0>vCL9{V7cgO%zevIVMH{WgP*^D9ws&OqA{yr|m| zKD4*07dGXshJHd#e%x%J+qmS^lS|0Bp?{drv;{@{l9ArPO&?Q5=?OO9=}h$oVe#3b z3Yofj&Cb}WC$PxmRRS)H%&$1-)z7jELS}!u!zQ?A^Y{Tv4QVt*vd@uj-^t2fYRzQj zfxGR>-q|o$3sGn^#VzZ!QQx?h9`njeJry}@x?|k0-GTTA4y3t2E`3DZ!A~D?GiJup z)8%PK2^9OVRlP(24P^4_<|D=H^7}WlWu#LgsdHzB%cPy|f8dD3|A^mh4WXxhLTVu_ z@abE{6Saz|Y{rXYPd4$tfPYo}ef(oQWZ=4Bct-=_9`#Qgp4ma$n$`tOwq#&E18$B; z@Bp)bn3&rEi0>fWWZ@7k5WazfoX`SCO4jQWwVuo+$PmSZn^Hz?O(-tW@*DGxuf)V1 zO_xm&;NVCaHD4dqt(-MlszI3F-p?0!-e$fbiCeuaw66h^TTDLWuaV<@C-`=Xe5WL) zwooG7h>4&*)p3pKMS3O!4>-4jQUN}iAMQ)2*70?hP~)TzzR?-f@?Aqy$$1Iy8VGG$ zMM?8;j!pUX7QQD$gRc_#+=raAS577ga-w?jd`vCiN5lu)dEUkkUPl9!?{$IJNxQys z*E4e$eF&n&+AMRQR2gcaFEjAy*r)G!s(P6D&TfoApMFC_*Ftx0|D0@E-=B7tezU@d zZ{hGiN;YLIoSeRS;9o%dEua4b%4R3;$SugDjP$x;Z!M!@QibuSBb)HY!3zJ7M;^jw zlx6AD50FD&p3JyP*>o+t9YWW8(7P2t!VQQ21pHJOcG_SXQD;(5aX#M6x##5H_Re>6lPyDCjxr*R(+HE%c&QN+b^tbT zXBJk?p)zhJj#I?&Y2n&~XiytG9!1ox;bw5Rbj~)7c(MFBb4>IiRATdhg zmiEFlj@S_hwYYI(ki{}&<;_7(Z0Qkfq>am z&LtL=2qc7rWguk3BtE4zL41@#S;NN*-jWw|7Kx7H7~_%7fPt;TIX}Ubo>;Rmj94V> zNB1=;-9AR7s`Pxn}t_6^3ahlq53e&!Lh85uG zec0vJY_6e`tg7LgfrJ3k!DjR)Bi#L@DHIrZ`sK=<5O0Ip!fxGf*OgGSpP@Hbbe&$9 z;ZI}8lEoC2_7;%L2=w?tb%1oL0V+=Z`7b=P&lNGY;yVBazXRYu;+cQDKvm*7NCxu&i;zub zAJh#11%?w>E2rf2e~C4+rAb-&$^vsdACs7 z@|Ra!OfVM(ke{vyiqh7puf&Yp6cd6{DptUteYfIRWG3pI+5< zBVBI_xkBAc<(pcb$!Y%dTW(b;B;2pOI-(QCsLv@U-D1XJ z(Gk8Q3l7Ws46Aktuj>|s{$6zA&xCPuXL-kB`CgYMs}4IeyG*P51IDwW?8UNQd+$i~ zlxOPtSi5L|gJcF@DwmJA5Ju8HEJ>o{{upwIpb!f{2(vLNBw`7xMbvcw<^{Fj@E~1( z?w`iIMieunS#>nXlmUcSMU+D3rX28f?s7z;X=se6bo8;5vM|O^(D6{A9*ChnGH!RG zP##3>LDC3jZPE4PH32AxrqPk|yIIrq~`aL-=}`okhNu9aT%q z1b)7iJ)CN=V#Ly84N_r7U^SH2FGdE5FpTO2 z630TF$P>GNMu8`rOytb(lB2};`;P4YNwW1<5d3Q~AX#P0aX}R2b2)`rgkp#zTxcGj zAV^cvFbhP|JgWrq_e`~exr~sIR$6p5V?o4Wym3kQ3HA+;Pr$bQ0(PmADVO%MKL!^q z?zAM8j1l4jrq|5X+V!8S*2Wl@=7*pPgciTVK6kS1Ge zMsd_u6DFK$jTnvVtE;qa+8(1sGBu~n&F%dh(&c(Zs4Fc#A=gG^^%^AyH}1^?|8quj zl@Z47h$){PlELJgYZCIHHL= z{U8O>Tw4x3<1{?$8>k-P<}1y9DmAZP_;(3Y*{Sk^H^A=_iSJ@+s5ktgwTXz_2$~W9>VVZsfwCm@s0sQ zeB50_yu@uS+e7QoPvdCwDz{prjo(AFwR%C?z`EL{1`|coJHQTk^nX=tvs1<0arUOJ z!^`*x&&BvTYmemyZ)2p~{%eYX=JVR?DYr(rNgqRMA5E1PR1Iw=prk=L2ldy3r3Vg@27IZx43+ywyzr-X*p*d@tZV+!U#~$-q=8c zgdSuh#r?b4GhEGNai)ayHQpk>5(%j5c@C1K3(W1pb~HeHpaqijJZa-e6vq_8t-^M^ zBJxq|MqZc?pjXPIH}70a5vt!IUh;l}<>VX<-Qcv^u@5(@@M2CHSe_hD$VG-eiV^V( zj7*9T0?di?P$FaD6oo?)<)QT>Npf6Og!GO^GmPV(Km0!=+dE&bk#SNI+C9RGQ|{~O*VC+tXK3!n`5 zHfl6>lwf_aEVV3`0T!aHNZLsj$paS$=LL(?b!Czaa5bbSuZ6#$_@LK<(7yrrl+80| z{tOFd=|ta2Z`^ssozD9BINn45NxUeCQis?-BKmU*Kt=FY-NJ+)8S1ecuFtN-M?&42 zl2$G>u!iNhAk*HoJ^4v^9#ORYp5t^wDj6|lx~5w45#E5wVqI1JQ~9l?nPp1YINf++ zMAdSif~_ETv@Er(EFBI^@L4BULFW>)NI+ejHFP*T}UhWNN`I)RRS8za? z*@`1>9ZB}An%aT5K=_2iQmfE;GcBVHLF!$`I99o5GO`O%O_zLr9AG18>&^HkG(;=V z%}c!OBQ~?MX(9h~tajX{=x)+!cbM7$YzTlmsPOdp2L-?GoW`@{lY9U3f;OUo*BwRB z8A+nv(br0-SH#VxGy#ZrgnGD(=@;HME;yd46EgWJ`EL%oXc&lFpc@Y}^>G(W>h_v_ zlN!`idhX+OjL+~T?19sroAFVGfa5tX-D49w$1g2g_-T|EpHL6}K_aX4$K=LTvwtlF zL*z}j{f+Uoe7{-px3_5iKPA<_7W=>Izkk)!l9ez2w%vi(?Y;i8AxRNLSOGDzNoqoI zP!1uAl}r=_871(G?y`i&)-7{u=%nxk7CZ_Qh#!|ITec zwQn`33GTUM`;D2POWnkqngqJhJRlM>CTONzTG}>^Q0wUunQyn|TAiHzyX2_%ATx%P z%7gW)%4rA9^)M<_%k@`Y?RbC<29sWU&5;@|9thf2#zf8z12$hRcZ!CSb>kUp=4N#y zl3hE#y6>kkA8VY2`W`g5Ip?2qC_BY$>R`iGQLhz2-S>x(RuWv)SPaGdl^)gGw7tjR zH@;jwk!jIaCgSg_*9iF|a);sRUTq30(8I(obh^|}S~}P4U^BIGYqcz;MPpC~Y@k_m zaw4WG1_vz2GdCAX!$_a%GHK**@IrHSkGoN>)e}>yzUTm52on`hYot7cB=oA-h1u|R ztH$11t?54Qg2L+i33FPFKKRm1aOjKST{l1*(nps`>sv%VqeVMWjl5+Gh+9);hIP8? zA@$?}Sc z3qIRpba+y5yf{R6G(u8Z^vkg0Fu&D-7?1s=QZU`Ub{-!Y`I?AGf1VNuc^L3v>)>i# z{DV9W$)>34wnzAXUiV^ZpYKw>UElrN_5Xj6{r_3| z$X5PK`e5$7>~9Dj7gK5ash(dvs`vwfk}&RD`>04;j62zoXESkFBklYaKm5seyiX(P zqQ-;XxlV*yg?Dhlx%xt!b0N3GHp@(p$A;8|%# zZ5m2KL|{on4nr>2_s9Yh=r5ScQ0;aMF)G$-9-Ca6%wA`Pa)i?NGFA|#Yi?{X-4ZO_ z^}%7%vkzvUHa$-^Y#aA+aiR5sa%S|Ebyn`EV<3Pc?ax_f>@sBZF1S;7y$CXd5t5=WGsTKBk8$OfH4v|0?0I=Yp}7c=WBSCg!{0n)XmiU;lfx)**zZaYqmDJelxk$)nZyx5`x$6R|fz(;u zEje5Dtm|a%zK!!tk3{i9$I2b{vXNFy%Bf{50X!x{98+BsDr_u9i>G5%*sqEX|06J0 z^IY{UcEbj6LDwuMh7cH`H@9sVt1l1#8kEQ(LyT@&+K}(ReE`ux8gb0r6L_#bDUo^P z3Ka2lRo52Hdtl_%+pwVs14=q`{d^L58PsU@AMf(hENumaxM{7iAT5sYmWh@hQCO^ zK&}ijo=`VqZ#a3vE?`7QW0ZREL17ZvDfdqKGD?0D4fg{7v%|Yj&_jcKJAB)>=*RS* zto8p6@k%;&^ZF>hvXm&$PCuEp{uqw3VPG$9VMdW5$w-fy2CNNT>E;>ejBgy-m_6`& z97L1p{%srn@O_JQgFpa_#f(_)eb#YS>o>q3(*uB;uZb605(iqM$=NK{nHY=+X2*G) zO3-_Xh%aG}fHWe*==58zBwp%&`mge<8uq8;xIxOd=P%9EK!34^E9sk|(Zq1QSz-JVeP12Fp)-`F|KY$LPwUE?rku zY@OJ)Z9A!ojfzfeyJ9;zv2EM7ZQB)AR5xGa-tMn^bl)FmoIiVyJ@!~@%{}qXXD&Ns zPnfe5U+&ohKefILu_1mPfLGuapX@btta5C#gPB2cjk5m4T}Nfi+Vfka!Yd(L?-c~5 z#ZK4VeQEXNPc4r$K00Fg>g#_W!YZ)cJ?JTS<&68_$#cZT-ME`}tcwqg3#``3M3UPvn+pi}(VNNx6y zFIMVb6OwYU(2`at$gHba*qrMVUl8xk5z-z~fb@Q3Y_+aXuEKH}L+>eW__!IAd@V}L zkw#s%H0v2k5-=vh$^vPCuAi22Luu3uKTf6fPo?*nvj$9(u)4$6tvF-%IM+3pt*cgs z_?wW}J7VAA{_~!?))?s6{M=KPpVhg4fNuU*|3THp@_(q!b*hdl{fjRVFWtu^1dV(f z6iOux9hi&+UK=|%M*~|aqFK{Urfl!TA}UWY#`w(0P!KMe1Si{8|o))Gy6d7;!JQYhgMYmXl?3FfOM2nQGN@~Ap6(G z3+d_5y@=nkpKAhRqf{qQ~k7Z$v&l&@m7Ppt#FSNzKPZM z8LhihcE6i=<(#87E|Wr~HKvVWhkll4iSK$^mUHaxgy8*K$_Zj;zJ`L$naPj+^3zTi z-3NTaaKnD5FPY-~?Tq6QHnmDDRxu0mh0D|zD~Y=vv_qig5r-cIbCpxlju&8Sya)@{ zsmv6XUSi)@(?PvItkiZEeN*)AE~I_?#+Ja-r8$(XiXei2d@Hi7Rx8+rZZb?ZLa{;@*EHeRQ-YDadz~M*YCM4&F-r;E#M+@CSJMJ0oU|PQ^ z=E!HBJDMQ2TN*Y(Ag(ynAL8%^v;=~q?s4plA_hig&5Z0x_^Oab!T)@6kRN$)qEJ6E zNuQjg|G7iwU(N8pI@_6==0CL;lRh1dQF#wePhmu@hADFd3B5KIH#dx(2A zp~K&;Xw}F_N6CU~0)QpQk7s$a+LcTOj1%=WXI(U=Dv!6 z{#<#-)2+gCyyv=Jw?Ab#PVkxPDeH|sAxyG`|Ys}A$PW4TdBv%zDz z^?lwrxWR<%Vzc8Sgt|?FL6ej_*e&rhqJZ3Y>k=X(^dytycR;XDU16}Pc9Vn0>_@H+ zQ;a`GSMEG64=JRAOg%~L)x*w{2re6DVprNp+FcNra4VdNjiaF0M^*>CdPkt(m150rCue?FVdL0nFL$V%5y6N z%eLr5%YN7D06k5ji5*p4v$UMM)G??Q%RB27IvH7vYr_^3>1D-M66#MN8tWGw>WED} z5AhlsanO=STFYFs)Il_0i)l)f<8qn|$DW7ZXhf5xI;m+7M5-%P63XFQrG9>DMqHc} zsgNU9nR`b}E^mL5=@7<1_R~j@q_2U^3h|+`7YH-?C=vme1C3m`Fe0HC>pjt6f_XMh zy~-i-8R46QNYneL4t@)<0VU7({aUO?aH`z4V2+kxgH5pYD5)wCh75JqQY)jIPN=U6 z+qi8cGiOtXG2tXm;_CfpH9ESCz#i5B(42}rBJJF$jh<1sbpj^8&L;gzGHb8M{of+} zzF^8VgML2O9nxBW7AvdEt90vp+#kZxWf@A)o9f9}vKJy9NDBjBW zSt=Hcs=YWCwnfY1UYx*+msp{g!w0HC<_SM!VL1(I2PE?CS}r(eh?{I)mQixmo5^p# zV?2R!R@3GV6hwTCrfHiK#3Orj>I!GS2kYhk1S;aFBD_}u2v;0HYFq}Iz1Z(I4oca4 zxquja8$+8JW_EagDHf$a1OTk5S97umGSDaj)gH=fLs9>_=XvVj^Xj9a#gLdk=&3tl zfmK9MNnIX9v{?%xdw7568 zNrZ|roYs(vC4pHB5RJ8>)^*OuyNC>x7ad)tB_}3SgQ96+-JT^Qi<`xi=)_=$Skwv~ zdqeT9Pa`LYvCAn&rMa2aCDV(TMI#PA5g#RtV|CWpgDYRA^|55LLN^uNh*gOU>Z=a06qJ;$C9z8;n-Pq=qZnc1zUwJ@t)L;&NN+E5m zRkQ(SeM8=l-aoAKGKD>!@?mWTW&~)uF2PYUJ;tB^my`r9n|Ly~0c%diYzqs9W#FTjy?h&X3TnH zXqA{QI82sdjPO->f=^K^f>N`+B`q9&rN0bOXO79S&a9XX8zund(kW7O76f4dcWhIu zER`XSMSFbSL>b;Rp#`CuGJ&p$s~G|76){d?xSA5wVg##_O0DrmyEYppyBr%fyWbbv zp`K84JwRNP$d-pJ!Qk|(RMr?*!wi1if-9G#0p>>1QXKXWFy)eB3ai)l3601q8!9JC zvU#ZWWDNKq9g6fYs?JQ)Q4C_cgTy3FhgKb8s&m)DdmL5zhNK#8wWg!J*7G7Qhe9VU zha?^AQTDpYcuN!B+#1dE*X{<#!M%zfUQbj=zLE{dW0XeQ7-oIsGY6RbkP2re@Q{}r_$iiH0xU%iN*ST`A)-EH6eaZB$GA#v)cLi z*MpA(3bYk$oBDKAzu^kJoSUsDd|856DApz={3u8sbQV@JnRkp2nC|)m;#T=DvIL-O zI4vh;g7824l}*`_p@MT4+d`JZ2%6NQh=N9bmgJ#q!hK@_<`HQq3}Z8Ij>3%~<*= zcv=!oT#5xmeGI92lqm9sGVE%#X$ls;St|F#u!?5Y7syhx6q#MVRa&lBmmn%$C0QzU z);*ldgwwCmzM3uglr}!Z2G+?& zf%Dpo&mD%2ZcNFiN-Z0f;c_Q;A%f@>26f?{d1kxIJD}LxsQkB47SAdwinfMILZdN3 zfj^HmTzS3Ku5BxY>ANutS8WPQ-G>v4^_Qndy==P3pDm+Xc?>rUHl-4+^%Sp5atOja z2oP}ftw-rqnb}+khR3CrRg^ibi6?QYk1*i^;kQGirQ=uB9Sd1NTfT-Rbv;hqnY4neE5H1YUrjS2m+2&@uXiAo- zrKUX|Ohg7(6F(AoP~tj;NZlV#xsfo-5reuQHB$&EIAhyZk;bL;k9ouDmJNBAun;H& zn;Of1z_Qj`x&M;5X;{s~iGzBQTY^kv-k{ksbE*Dl%Qf%N@hQCfY~iUw!=F-*$cpf2 z3wix|aLBV0b;W@z^%7S{>9Z^T^fLOI68_;l@+Qzaxo`nAI8emTV@rRhEKZ z?*z_{oGdI~R*#<2{bkz$G~^Qef}$*4OYTgtL$e9q!FY7EqxJ2`zk6SQc}M(k(_MaV zSLJnTXw&@djco1~a(vhBl^&w=$fa9{Sru>7g8SHahv$&Bl(D@(Zwxo_3r=;VH|uc5 zi1Ny)J!<(KN-EcQ(xlw%PNwK8U>4$9nVOhj(y0l9X^vP1TA>r_7WtSExIOsz`nDOP zs}d>Vxb2Vo2e5x8p(n~Y5ggAyvib>d)6?)|E@{FIz?G3PVGLf7-;BxaP;c?7ddH$z zA+{~k^V=bZuXafOv!RPsE1GrR3J2TH9uB=Z67gok+u`V#}BR86hB1xl}H4v`F+mRfr zYhortD%@IGfh!JB(NUNSDh+qDz?4ztEgCz&bIG-Wg7w-ua4ChgQR_c+z8dT3<1?uX z*G(DKy_LTl*Ea!%v!RhpCXW1WJO6F`bgS-SB;Xw9#! z<*K}=#wVu9$`Yo|e!z-CPYH!nj7s9dEPr-E`DXUBu0n!xX~&|%#G=BeM?X@shQQMf zMvr2!y7p_gD5-!Lnm|a@z8Of^EKboZsTMk%5VsJEm>VsJ4W7Kv{<|#4f-qDE$D-W>gWT%z-!qXnDHhOvLk=?^a1*|0j z{pW{M0{#1VcR5;F!!fIlLVNh_Gj zbnW(_j?0c2q$EHIi@fSMR{OUKBcLr{Y&$hrM8XhPByyZaXy|dd&{hYQRJ9@Fn%h3p7*VQolBIV@Eq`=y%5BU~3RPa^$a?ixp^cCg z+}Q*X+CW9~TL29@OOng(#OAOd!)e$d%sr}^KBJ-?-X&|4HTmtemxmp?cT3uA?md4% zT8yZ0U;6Rg6JHy3fJae{6TMGS?ZUX6+gGTT{Q{)SI85$5FD{g-eR%O0KMpWPY`4@O zx!hen1*8^E(*}{m^V_?}(b5k3hYo=T+$&M32+B`}81~KKZhY;2H{7O-M@vbCzuX0n zW-&HXeyr1%I3$@ns-V1~Lb@wIpkmx|8I~ob1Of7i6BTNysEwI}=!nU%q7(V_^+d*G z7G;07m(CRTJup!`cdYi93r^+LY+`M*>aMuHJm(A8_O8C#A*$!Xvddgpjx5)?_EB*q zgE8o5O>e~9IiSC@WtZpF{4Bj2J5eZ>uUzY%TgWF7wdDE!fSQIAWCP)V{;HsU3ap?4 znRsiiDbtN7i9hapO;(|Ew>Ip2TZSvK9Z^N21%J?OiA_&eP1{(Pu_=%JjKy|HOardq ze?zK^K zA%sjF64*Wufad%H<) z^|t>e*h+Z1#l=5wHexzt9HNDNXgM=-OPWKd^5p!~%SIl>Fo&7BvNpbf8{NXmH)o{r zO=aBJ;meX1^{O%q;kqdw*5k!Y7%t_30 zy{nGRVc&5qt?dBwLs+^Sfp;f`YVMSB#C>z^a9@fpZ!xb|b-JEz1LBX7ci)V@W+kvQ89KWA0T~Lj$aCcfW#nD5bt&Y_< z-q{4ZXDqVg?|0o)j1%l0^_it0WF*LCn-+)c!2y5yS7aZIN$>0LqNnkujV*YVes(v$ zY@_-!Q;!ZyJ}Bg|G-~w@or&u0RO?vlt5*9~yeoPV_UWrO2J54b4#{D(D>jF(R88u2 zo#B^@iF_%S>{iXSol8jpmsZuJ?+;epg>k=$d`?GSegAVp3n$`GVDvK${N*#L_1`44 z{w0fL{2%)0|E+qgZtjX}itZz^KJt4Y;*8uSK}Ft38+3>j|K(PxIXXR-t4VopXo#9# zt|F{LWr-?34y`$nLBVV_*UEgA6AUI65dYIbqpNq9cl&uLJ0~L}<=ESlOm?Y-S@L*d z<7vt}`)TW#f%Rp$Q}6@3=j$7Tze@_uZO@aMn<|si{?S}~maII`VTjs&?}jQ4_cut9$)PEqMukwoXobzaKx^MV z2fQwl+;LSZ$qy%Tys0oo^K=jOw$!YwCv^ei4NBVauL)tN%=wz9M{uf{IB(BxK|lT*pFkmNK_1tV`nb%jH=a0~VNq2RCKY(rG7jz!-D^k)Ec)yS%17pE#o6&eY+ z^qN(hQT$}5F(=4lgNQhlxj?nB4N6ntUY6(?+R#B?W3hY_a*)hnr4PA|vJ<6p`K3Z5Hy z{{8(|ux~NLUW=!?9Qe&WXMTAkQnLXg(g=I@(VG3{HE13OaUT|DljyWXPs2FE@?`iU z4GQlM&Q=T<4&v@Fe<+TuXiZQT3G~vZ&^POfmI1K2h6t4eD}Gk5XFGpbj1n_g*{qmD6Xy z`6Vv|lLZtLmrnv*{Q%xxtcWVj3K4M%$bdBk_a&ar{{GWyu#ljM;dII;*jP;QH z#+^o-A4np{@|Mz+LphTD0`FTyxYq#wY)*&Ls5o{0z9yg2K+K7ZN>j1>N&;r+Z`vI| zDzG1LJZ+sE?m?>x{5LJx^)g&pGEpY=fQ-4}{x=ru;}FL$inHemOg%|R*ZXPodU}Kh zFEd5#+8rGq$Y<_?k-}r5zgQ3jRV=ooHiF|@z_#D4pKVEmn5CGV(9VKCyG|sT9nc=U zEoT67R`C->KY8Wp-fEcjjFm^;Cg(ls|*ABVHq8clBE(;~K^b+S>6uj70g? z&{XQ5U&!Z$SO7zfP+y^8XBbiu*Cv-yJG|l-oe*!s5$@Lh_KpxYL2sx`B|V=dETN>5K+C+CU~a_3cI8{vbu$TNVdGf15*>D zz@f{zIlorkY>TRh7mKuAlN9A0>N>SV`X)+bEHms=mfYTMWt_AJtz_h+JMmrgH?mZt zm=lfdF`t^J*XLg7v+iS)XZROygK=CS@CvUaJo&w2W!Wb@aa?~Drtf`JV^cCMjngVZ zv&xaIBEo8EYWuML+vxCpjjY^s1-ahXJzAV6hTw%ZIy!FjI}aJ+{rE&u#>rs)vzuxz z+$5z=7W?zH2>Eb32dvgHYZtCAf!=OLY-pb4>Ae79rd68E2LkVPj-|jFeyqtBCCwiW zkB@kO_(3wFq)7qwV}bA=zD!*@UhT`geq}ITo%@O(Z5Y80nEX~;0-8kO{oB6|(4fQh z);73T!>3@{ZobPwRv*W?7m0Ml9GmJBCJd&6E?hdj9lV= z4flNfsc(J*DyPv?RCOx!MSvk(M952PJ-G|JeVxWVjN~SNS6n-_Ge3Q;TGE;EQvZg86%wZ`MB zSMQua(i*R8a75!6$QRO^(o7sGoomb+Y{OMy;m~Oa`;P9Yqo>?bJAhqXxLr7_3g_n>f#UVtxG!^F#1+y@os6x(sg z^28bsQ@8rw%Gxk-stAEPRbv^}5sLe=VMbkc@Jjimqjvmd!3E7+QnL>|(^3!R} zD-l1l7*Amu@j+PWLGHXXaFG0Ct2Q=}5YNUxEQHCAU7gA$sSC<5OGylNnQUa>>l%sM zyu}z6i&({U@x^hln**o6r2s-(C-L50tQvz|zHTqW!ir?w&V23tuYEDJVV#5pE|OJu z7^R!A$iM$YCe?8n67l*J-okwfZ+ZTkGvZ)tVPfR;|3gyFjF)8V zyXXN=!*bpyRg9#~Bg1+UDYCt0 ztp4&?t1X0q>uz;ann$OrZs{5*r`(oNvw=$7O#rD|Wuv*wIi)4b zGtq4%BX+kkagv3F9Id6~-c+1&?zny%w5j&nk9SQfo0k4LhdSU_kWGW7axkfpgR`8* z!?UTG*Zi_baA1^0eda8S|@&F z{)Rad0kiLjB|=}XFJhD(S3ssKlveFFmkN{Vl^_nb!o5M!RC=m)V&v2%e?ZoRC@h3> zJ(?pvToFd`*Zc@HFPL#=otWKwtuuQ_dT-Hr{S%pQX<6dqVJ8;f(o)4~VM_kEQkMR+ zs1SCVi~k>M`u1u2xc}>#D!V&6nOOh-E$O&SzYrjJdZpaDv1!R-QGA141WjQe2s0J~ zQ;AXG)F+K#K8_5HVqRoRM%^EduqOnS(j2)|ctA6Q^=|s_WJYU;Z%5bHp08HPL`YF2 zR)Ad1z{zh`=sDs^&V}J z%$Z$!jd7BY5AkT?j`eqMs%!Gm@T8)4w3GYEX~IwgE~`d|@T{WYHkudy(47brgHXx& zBL1yFG6!!!VOSmDxBpefy2{L_u5yTwja&HA!mYA#wg#bc-m%~8aRR|~AvMnind@zs zy>wkShe5&*un^zvSOdlVu%kHsEo>@puMQ`b1}(|)l~E{5)f7gC=E$fP(FC2=F<^|A zxeIm?{EE!3sO!Gr7e{w)Dx(uU#3WrFZ>ibmKSQ1tY?*-Nh1TDHLe+k*;{Rp!Bmd_m zb#^kh`Y*8l|9Cz2e{;RL%_lg{#^Ar+NH|3z*Zye>!alpt{z;4dFAw^^H!6ING*EFc z_yqhr8d!;%nHX9AKhFQZBGrSzfzYCi%C!(Q5*~hX>)0N`vbhZ@N|i;_972WSx*>LH z87?en(;2_`{_JHF`Sv6Wlps;dCcj+8IJ8ca6`DsOQCMb3n# z3)_w%FuJ3>fjeOOtWyq)ag|PmgQbC-s}KRHG~enBcIwqIiGW8R8jFeBNY9|YswRY5 zjGUxdGgUD26wOpwM#8a!Nuqg68*dG@VM~SbOroL_On0N6QdT9?)NeB3@0FCC?Z|E0 z6TPZj(AsPtwCw>*{eDEE}Gby>0q{*lI+g2e&(YQrsY&uGM{O~}(oM@YWmb*F zA0^rr5~UD^qmNljq$F#ARXRZ1igP`MQx4aS6*MS;Ot(1L5jF2NJ;de!NujUYg$dr# z=TEL_zTj2@>ZZN(NYCeVX2==~=aT)R30gETO{G&GM4XN<+!&W&(WcDP%oL8PyIVUC zs5AvMgh6qr-2?^unB@mXK*Dbil^y-GTC+>&N5HkzXtozVf93m~xOUHn8`HpX=$_v2 z61H;Z1qK9o;>->tb8y%#4H)765W4E>TQ1o0PFj)uTOPEvv&}%(_mG0ISmyhnQV33Z$#&yd{ zc{>8V8XK$3u8}04CmAQ#I@XvtmB*s4t8va?-IY4@CN>;)mLb_4!&P3XSw4pA_NzDb zORn!blT-aHk1%Jpi>T~oGLuh{DB)JIGZ9KOsciWs2N7mM1JWM+lna4vkDL?Q)z_Ct z`!mi0jtr+4*L&N7jk&LodVO#6?_qRGVaucqVB8*us6i3BTa^^EI0x%EREQSXV@f!lak6Wf1cNZ8>*artIJ(ADO*=<-an`3zB4d*oO*8D1K!f z*A@P1bZCNtU=p!742MrAj%&5v%Xp_dSX@4YCw%F|%Dk=u|1BOmo)HsVz)nD5USa zR~??e61sO(;PR)iaxK{M%QM_rIua9C^4ppVS$qCT9j2%?*em?`4Z;4@>I(c%M&#cH z>4}*;ej<4cKkbCAjjDsyKS8rIm90O)Jjgyxj5^venBx&7B!xLmzxW3jhj7sR(^3Fz z84EY|p1NauwXUr;FfZjdaAfh%ivyp+^!jBjJuAaKa!yCq=?T_)R!>16?{~p)FQ3LDoMyG%hL#pR!f@P%*;#90rs_y z@9}@r1BmM-SJ#DeuqCQk=J?ixDSwL*wh|G#us;dd{H}3*-Y7Tv5m=bQJMcH+_S`zVtf;!0kt*(zwJ zs+kedTm!A}cMiM!qv(c$o5K%}Yd0|nOd0iLjus&;s0Acvoi-PFrWm?+q9f^FslxGi z6ywB`QpL$rJzWDg(4)C4+!2cLE}UPCTBLa*_=c#*$b2PWrRN46$y~yST3a2$7hEH= zNjux+wna^AzQ=KEa_5#9Ph=G1{S0#hh1L3hQ`@HrVnCx{!fw_a0N5xV(iPdKZ-HOM za)LdgK}1ww*C_>V7hbQnTzjURJL`S%`6nTHcgS+dB6b_;PY1FsrdE8(2K6FN>37!62j_cBlui{jO^$dPkGHV>pXvW0EiOA zqW`YaSUBWg_v^Y5tPJfWLcLpsA8T zG)!x>pKMpt!lv3&KV!-um= zKCir6`bEL_LCFx4Z5bAFXW$g3Cq`?Q%)3q0r852XI*Der*JNuKUZ`C{cCuu8R8nkt z%pnF>R$uY8L+D!V{s^9>IC+bmt<05h**>49R*#vpM*4i0qRB2uPbg8{{s#9yC;Z18 zD7|4m<9qneQ84uX|J&f-g8a|nFKFt34@Bt{CU`v(SYbbn95Q67*)_Esl_;v291s=9 z+#2F2apZU4Tq=x+?V}CjwD(P=U~d<=mfEFuyPB`Ey82V9G#Sk8H_Ob_RnP3s?)S_3 zr%}Pb?;lt_)Nf>@zX~D~TBr;-LS<1I##8z`;0ZCvI_QbXNh8Iv)$LS=*gHr;}dgb=w5$3k2la1keIm|=7<-JD>)U%=Avl0Vj@+&vxn zt-)`vJxJr88D&!}2^{GPXc^nmRf#}nb$4MMkBA21GzB`-Or`-3lq^O^svO7Vs~FdM zv`NvzyG+0T!P8l_&8gH|pzE{N(gv_tgDU7SWeiI-iHC#0Ai%Ixn4&nt{5y3(GQs)i z&uA;~_0shP$0Wh0VooIeyC|lak__#KVJfxa7*mYmZ22@(<^W}FdKjd*U1CqSjNKW% z*z$5$=t^+;Ui=MoDW~A7;)Mj%ibX1_p4gu>RC}Z_pl`U*{_z@+HN?AF{_W z?M_X@o%w8fgFIJ$fIzBeK=v#*`mtY$HC3tqw7q^GCT!P$I%=2N4FY7j9nG8aIm$c9 zeKTxVKN!UJ{#W)zxW|Q^K!3s;(*7Gbn;e@pQBCDS(I|Y0euK#dSQ_W^)sv5pa%<^o zyu}3d?Lx`)3-n5Sy9r#`I{+t6x%I%G(iewGbvor&I^{lhu-!#}*Q3^itvY(^UWXgvthH52zLy&T+B)Pw;5>4D6>74 zO_EBS)>l!zLTVkX@NDqyN2cXTwsUVao7$HcqV2%t$YzdAC&T)dwzExa3*kt9d(}al zA~M}=%2NVNUjZiO7c>04YH)sRelXJYpWSn^aC$|Ji|E13a^-v2MB!Nc*b+=KY7MCm zqIteKfNkONq}uM;PB?vvgQvfKLPMB8u5+Am=d#>g+o&Ysb>dX9EC8q?D$pJH!MTAqa=DS5$cb+;hEvjwVfF{4;M{5U&^_+r zvZdu_rildI!*|*A$TzJ&apQWV@p{!W`=?t(o0{?9y&vM)V)ycGSlI3`;ps(vf2PUq zX745#`cmT*ra7XECC0gKkpu2eyhFEUb?;4@X7weEnLjXj_F~?OzL1U1L0|s6M+kIhmi%`n5vvDALMagi4`wMc=JV{XiO+^ z?s9i7;GgrRW{Mx)d7rj)?(;|b-`iBNPqdwtt%32se@?w4<^KU&585_kZ=`Wy^oLu9 z?DQAh5z%q;UkP48jgMFHTf#mj?#z|=w= z(q6~17Vn}P)J3M?O)x))%a5+>TFW3No~TgP;f}K$#icBh;rSS+R|}l鯊%1Et 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⁢ zv5o9#E7!G0XmdzfsS{efPNi`1b44~SZ4Z8fuX!I}#8g+(wxzQwUT#Xb2(tbY1+EUhGKoT@KEU9Ktl>_0 z%bjDJg;#*gtJZv!-Zs`?^}v5eKmnbjqlvnSzE@_SP|LG_PJ6CYU+6zY6>92%E+ z=j@TZf-iW4(%U{lnYxQA;7Q!b;^brF8n0D>)`q5>|WDDXLrqYU_tKN2>=#@~OE7grMnNh?UOz-O~6 z6%rHy{#h9K0AT+lDC7q4{hw^|q6*Ry;;L%Q@)Ga}$60_q%D)rv(CtS$CQbpq9|y1e zRSrN4;$Jyl{m5bZw`$8TGvb}(LpY{-cQ)fcyJv7l3S52TLXVDsphtv&aPuDk1OzCA z4A^QtC(!11`IsNx_HnSy?>EKpHJWT^wmS~hc^p^zIIh@9f6U@I2 zC=Mve{j2^)mS#U$e{@Q?SO6%LDsXz@SY+=cK_QMmXBIU)j!$ajc-zLx3V60EXJ!qC zi<%2x8Q24YN+&8U@CIlN zrZkcT9yh%LrlGS9`G)KdP(@9Eo-AQz@8GEFWcb7U=a0H^ZVbLmz{+&M7W(nXJ4sN8 zJLR7eeK(K8`2-}j(T7JsO`L!+CvbueT%izanm-^A1Dn{`1Nw`9P?cq;7no+XfC`K(GO9?O^5zNIt4M+M8LM0=7Gz8UA@Z0N+lg+cX)NfazRu z5D)~HA^(u%w^cz+@2@_#S|u>GpB+j4KzQ^&Wcl9f z&hG#bCA(Yk0D&t&aJE^xME^&E-&xGHhXn%}psEIj641H+Nl-}boj;)Zt*t(4wZ5DN z@GXF$bL=&pBq-#vkTkh>7hl%K5|3 z{`Vn9b$iR-SoGENp}bn4;fR3>9sA%X2@1L3aE9yTra;Wb#_`xWwLSLdfu+PAu+o3| zGVnpzPr=ch{uuoHjtw7+_!L_2;knQ!DuDl0R`|%jr+}jFzXtrHIKc323?JO{l&;VF z*L1+}JU7%QJOg|5|Tc|D8fN zJORAg=_vsy{ak|o);@)Yh8Lkcg@$FG3k@ep36BRa^>~UmnRPziS>Z=`Jb2x*Q#`%A zU*i3&Vg?TluO@X0O;r2Jl6LKLUOVhSqg1*qOt^|8*c7 zo(298@+r$k_wQNGHv{|$tW(T8L+4_`FQ{kEW5Jgg{yf7ey4ss_(SNKfz(N9lx&a;< je(UuV8hP?p&}TPdm1I$XmG#(RzlD&B2izSj9sl%y5~4qc diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index a80b22ce5..a4413138c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.6-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip networkTimeout=10000 validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME diff --git a/gradlew.bat b/gradlew.bat index 93e3f59f1..25da30dbd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -43,11 +43,11 @@ set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -57,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail From ff26cf95739346dda45f006ac02865b5adf2915b Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 24 Jun 2024 12:48:45 +0200 Subject: [PATCH 18/22] Updating/fixing changelog for release --- CHANGELOG.md | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 04c2476f3..a1184b6cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,14 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Added + +### Fixed + +### Changed + +## [5.1.0] - 2024-06-24 + ### Added - Enhancing `VoltageLevel` with `equals` method [#1063](https://github.com/ie3-institute/PowerSystemDataModel/issues/1063) - `ConnectorValidationUtils` checks if parallel devices is > 0 [#1077](https://github.com/ie3-institute/PowerSystemDataModel/issues/1077) @@ -294,9 +302,10 @@ coordinates or multiple exactly equal coordinates possible - CsvDataSource now stops trying to get an operator for empty operator uuid field in entities - CsvDataSource now parsing multiple geoJson strings correctly -[Unreleased/Snapshot]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.1...HEAD -[5.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.0...5.0.1 -[4.1.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/4.1.0...5.0.0 +[Unreleased/Snapshot]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.1.0...HEAD +[5.1.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.1...5.1.0 +[5.0.1]: https://github.com/ie3-institute/powersystemdatamodel/compare/5.0.0...5.0.1 +[5.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/4.1.0...5.0.0 [4.1.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/4.0.0...4.1.0 [4.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/3.0.0...4.0.0 [3.0.0]: https://github.com/ie3-institute/powersystemdatamodel/compare/2.1.0...3.0.0 From cad964d3bb3348340dcc03a8f60474093a76c2b0 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 24 Jun 2024 12:56:41 +0200 Subject: [PATCH 19/22] Fixed misspelling --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a1184b6cf..083590698 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,7 +23,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed `MappingEntryies` not getting processed by adding `Getter` methods for record fields [#1084](https://github.com/ie3-institute/PowerSystemDataModel/issues/1084) - Fixed "depth of discharge" in documentation [#872](https://github.com/ie3-institute/PowerSystemDataModel/issues/872) -- Fixed project beiing build twice in CI [#994](https://github.com/ie3-institute/PowerSystemDataModel/issues/994) +- Fixed project being build twice in CI [#994](https://github.com/ie3-institute/PowerSystemDataModel/issues/994) ### Changed - Improvements to the search for corner points in `IdCoordinateSource` [#1016](https://github.com/ie3-institute/PowerSystemDataModel/issues/1016) From 334d50ec50b5b5fdcd0974fc55115128ac96b777 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 24 Jun 2024 13:26:27 +0200 Subject: [PATCH 20/22] snapshot version --- version.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version.properties b/version.properties index f9c849a07..18a1ed5d0 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Mon Jun 24 12:43:47 CEST 2024 +#Mon Jun 24 13:26:11 CEST 2024 version.buildmeta= -version.major=5 -version.minor=1 +version.major=6 +version.minor=0 version.patch=0 version.prerelease= -version.semver=5.1.0 +version.semver=6.0.0 From 9dc572a2523ebaace4f85aee7176fabdc3a21a26 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 28 Jun 2024 09:44:52 +0000 Subject: [PATCH 21/22] Bump org.junit.jupiter:junit-jupiter from 5.10.2 to 5.10.3 (#1110) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 3a97a0b6a..a277cc41d 100644 --- a/build.gradle +++ b/build.gradle @@ -70,7 +70,7 @@ dependencies { // testing testImplementation "org.apache.groovy:groovy:$groovyBinaryVersion" - testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' + testImplementation 'org.junit.jupiter:junit-jupiter:5.10.3' testImplementation "org.spockframework:spock-core:2.3-groovy-$groovyVersion" testImplementation 'org.objenesis:objenesis:3.4' // Mock creation with constructor parameters testImplementation 'net.bytebuddy:byte-buddy:1.14.17' // Mocks of classes From 1623d7fce7bb8f387e07fa82f24712944d974c3e Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:50:32 +0000 Subject: [PATCH 22/22] Bump org.apache.groovy:groovy from 4.0.21 to 4.0.22 (#1111) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index a277cc41d..e39a59206 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ ext { //version (changing these should be considered thoroughly!) javaVersion = JavaVersion.VERSION_17 groovyVersion = "4.0" - groovyBinaryVersion = "4.0.21" + groovyBinaryVersion = "4.0.22" testcontainersVersion = '1.19.8' scriptsLocation = 'gradle' + File.separator + 'scripts' + File.separator //location of script plugins

type of zipped data + */ + public abstract Try zip(Try that, BiFunction zipper); + /** * Method to convert a {@link Try} object to a common type. * @@ -370,6 +394,11 @@ public Try transform( return new Success<>(successFunc.apply(data)); } + @Override + public Try zip(Try that, BiFunction zipper) { + return that.map(thatData -> zipper.apply(data, thatData)); + } + @Override public U convert(Function successFunc, Function failureFunc) { return successFunc.apply(data); @@ -505,6 +534,11 @@ public Try transform( return Failure.of(failureFunc.apply(exception)); } + @Override + public Try zip(Try that, BiFunction zipper) { + return Failure.of(exception); + } + @Override public U convert(Function successFunc, Function failureFunc) { return failureFunc.apply(exception); From 8bd4ee020eb470dcb9aefb172f09d6bbcbf17f8d Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 12 Mar 2024 13:43:58 +0100 Subject: [PATCH 03/22] Adapting tests. Some improvements --- CHANGELOG.md | 11 + .../io/source/AssetEntitySource.java | 41 +- .../io/source/EnergyManagementSource.java | 46 +-- .../ie3/datamodel/io/source/EntitySource.java | 358 ++++++++--------- .../datamodel/io/source/GraphicSource.java | 17 +- .../datamodel/io/source/RawGridSource.java | 34 +- .../io/source/ResultEntitySource.java | 4 +- .../io/source/SystemParticipantSource.java | 112 +++--- .../datamodel/io/source/ThermalSource.java | 13 +- .../io/source/AssetEntitySourceTest.groovy | 238 +++--------- .../io/source/DummyDataSource.groovy | 34 ++ .../io/source/EntitySourceTest.groovy | 193 +++++++++ .../source/SystemParticipantSourceTest.groovy | 114 ++---- .../io/source/ThermalSourceTest.groovy | 53 +-- .../io/source/csv/CsvGraphicSourceTest.groovy | 2 +- .../io/source/csv/CsvRawGridSourceTest.groovy | 367 ++---------------- .../csv/CsvSystemParticipantSourceTest.groovy | 24 +- 17 files changed, 667 insertions(+), 994 deletions(-) create mode 100644 src/test/groovy/edu/ie3/datamodel/io/source/DummyDataSource.groovy create mode 100644 src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy diff --git a/CHANGELOG.md b/CHANGELOG.md index b431ade12..3fb29ee4e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,17 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] +### Added + + +### Fixed + + +### Changed +- Make `EntitySource` completely static [#975](https://github.com/ie3-institute/PowerSystemDataModel/issues/975) +- Abstract commonly used functionality from `EntitySource` [#981](https://github.com/ie3-institute/PowerSystemDataModel/issues/981) + + ## [5.0.1] - 2024-03-07 ### Fixed diff --git a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java index 80b4dfe94..14a269358 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java @@ -10,14 +10,13 @@ import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.io.factory.EntityData; import edu.ie3.datamodel.io.factory.input.*; -import edu.ie3.datamodel.io.factory.input.participant.SystemParticipantEntityData; import edu.ie3.datamodel.models.input.AssetTypeInput; import edu.ie3.datamodel.models.input.NodeInput; import edu.ie3.datamodel.models.input.OperatorInput; import edu.ie3.datamodel.models.input.connector.ConnectorInput; -import edu.ie3.datamodel.utils.Try; import java.util.Map; import java.util.UUID; +import java.util.function.BiFunction; import java.util.stream.Stream; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -40,22 +39,16 @@ public abstract class AssetEntitySource extends EntitySource { protected static final EnrichFunction assetEnricher = (data, operators) -> - enrich( - data, - buildEnrichmentWithDefault(data, OPERATOR, operators, NO_OPERATOR_ASSIGNED), - AssetInputEntityData::new); + enrichWithDefault( + OPERATOR, operators, NO_OPERATOR_ASSIGNED, AssetInputEntityData::new) + .apply(data); protected static final BiEnrichFunction< EntityData, OperatorInput, NodeInput, NodeAssetInputEntityData> nodeAssetEnricher = (data, operators, nodes) -> assetEnricher - .andThen( - enrichedData -> - enrich( - enrichedData, - buildEnrichment(enrichedData, NODE, nodes), - NodeAssetInputEntityData::new)) + .andThen(enrich(NODE, nodes, NodeAssetInputEntityData::new)) .apply(data, operators); protected static final BiEnrichFunction< @@ -63,13 +56,7 @@ public abstract class AssetEntitySource extends EntitySource { connectorEnricher = (data, operators, nodes) -> assetEnricher - .andThen( - assetData -> - biEnrich( - assetData, - buildEnrichment(data, NODE_A, nodes), - buildEnrichment(data, NODE_B, nodes), - ConnectorInputEntityData::new)) + .andThen(biEnrich(NODE_A, nodes, NODE_B, nodes, ConnectorInputEntityData::new)) .apply(data, operators); protected AssetEntitySource(DataSource dataSource) { @@ -92,7 +79,7 @@ protected AssetEntitySource(DataSource dataSource) { * @param type of asset types * @throws SourceException if an error happens during reading */ - protected + protected static Stream getTypedConnectorEntities( Class entityClass, DataSource dataSource, @@ -105,23 +92,19 @@ Stream getTypedConnectorEntities( entityClass, dataSource, factory, - data -> - connectorEnricher - .andThen(connectorData -> enrich(connectorData, types)) - .apply(data, operators, nodes)); + data -> connectorEnricher.andThen(enrich(types)).apply(data, operators, nodes)); } /** - * Method for enriching {@link SystemParticipantEntityData} with types. + * Builds a function for enriching {@link ConnectorInputEntityData} with types. * - * @param data to enrich * @param types all known types * @return a typed entity data * @param type of types */ private static - Try, SourceException> enrich( - Try data, Map types) { - return enrich(data, buildEnrichment(data, TYPE, types), TypedConnectorInputEntityData::new); + TryFunction> enrich(Map types) { + BiFunction> fcn = TypedConnectorInputEntityData::new; + return entityData -> enrich(TYPE, types, fcn).apply(entityData); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java index b5ad92c5a..3396b02e5 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java @@ -74,9 +74,9 @@ public Map getEmUnits(Map operators) throws /** * Since each EM can itself be controlled by another EM, it does not suffice to link {@link - * EmInput}s via {@link EntitySource#buildEnrichmentWithDefault} as we do for system participants - * in {@link SystemParticipantSource}. Instead, we use a recursive approach, starting with EMs at - * root level (which are not EM-controlled themselves). + * EmInput}s via {@link EntitySource#enrich} as we do for system participants in {@link + * SystemParticipantSource}. Instead, we use a recursive approach, starting with EMs at root level + * (which are not EM-controlled themselves). * * @param assetEntityDataStream the data stream of {@link AssetInputEntityData} {@link Try} * objects @@ -104,15 +104,16 @@ private static Map createEmInputs( // at the start, this is only root ems Map allEms = - unpackMap( - rootEmsEntityData.stream() - .parallel() - .map( - entityDataTry -> - entityDataTry.map( - entityData -> new EmAssetInputEntityData(entityData, null))) - .map(emInputFactory::get), - EmInput.class); + unpack( + rootEmsEntityData.stream() + .parallel() + .map( + entityDataTry -> + entityDataTry.map( + entityData -> new EmAssetInputEntityData(entityData, null))) + .map(emInputFactory::get), + EmInput.class) + .collect(toMap()); if (!others.isEmpty()) { // there's more EM levels beyond root level. Build them recursively @@ -176,16 +177,17 @@ private static Map createHierarchicalEmInputs( } else { // New EMs can be built at this level Map newEms = - unpackMap( - toBeBuiltAtThisLevel.stream() - .map( - data -> { - // exists because we checked above - EmInput parentEm = lastLevelEms.get(data.parentEm); - return emInputFactory.get( - new EmAssetInputEntityData(data.entityData, parentEm)); - }), - EmInput.class); + unpack( + toBeBuiltAtThisLevel.stream() + .map( + data -> { + // exists because we checked above + EmInput parentEm = lastLevelEms.get(data.parentEm); + return emInputFactory.get( + new EmAssetInputEntityData(data.entityData, parentEm)); + }), + EmInput.class) + .collect(toMap()); if (!toBeBuiltAtNextLevel.isEmpty()) { // If there's more EMs left to build, the new EMs have to function as parents there diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index 21e9b1fd9..0f4054202 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -17,15 +17,13 @@ import edu.ie3.datamodel.utils.TriFunction; import edu.ie3.datamodel.utils.Try; import edu.ie3.datamodel.utils.Try.Failure; -import java.util.Map; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; +import java.util.*; import java.util.function.BiFunction; import java.util.function.Function; import java.util.stream.Collector; import java.util.stream.Collectors; import java.util.stream.Stream; +import org.apache.commons.lang3.tuple.Pair; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -36,14 +34,14 @@ public abstract class EntitySource { protected static final Logger log = LoggerFactory.getLogger(EntitySource.class); - // default collectors + // convenience collectors protected static Collector> toMap() { return Collectors.toMap(UniqueEntity::getUuid, Function.identity()); } - protected static Set toSet(Stream stream) { - return stream.collect(Collectors.toSet()); + protected static Collector> toSet() { + return Collectors.toSet(); } protected EntitySource() {} @@ -63,7 +61,7 @@ protected EntitySource() {} * @param validator used to validate * @param type of the class */ - protected final Try validate( + protected static Try validate( Class entityClass, DataSource dataSource, SourceValidator validator) { return validate(entityClass, () -> dataSource.getSourceFields(entityClass), validator); } @@ -76,7 +74,7 @@ protected final Try validate( * @param validator used to validate * @param type of the class */ - protected final Try validate( + protected static Try validate( Class entityClass, Try.TrySupplier>, SourceException> sourceFields, SourceValidator validator) { @@ -96,6 +94,52 @@ protected final Try validate( .orElse(Try.Success.empty())); } + /** + * Universal method to get a map: uuid to {@link UniqueEntity}. + * + * @param entityClass subclass of {@link UniqueEntity} + * @param dataSource source for the data + * @param factory to build the entity + * @return a map: uuid to {@link UniqueEntity} + * @param type of entity + * @throws SourceException - if an error happen during reading + */ + @SuppressWarnings("unchecked") + protected static Map getEntities( + Class entityClass, + DataSource dataSource, + EntityFactory factory) + throws SourceException { + return unpack( + buildEntityData(entityClass, dataSource) + .map(data -> (Try) factory.get(data)), + entityClass) + .collect(toMap()); + } + + /** + * Universal method to get a {@link Entity} stream. + * + * @param entityClass class of the entity + * @param dataSource source for the entity + * @param factory to build the entity + * @param fcn function to enrich the given entity data + * @return a set of {@link Entity}s + * @param type of entity + * @param type of entity data + * @throws SourceException - if an error happen during reading + */ + protected static Stream getEntities( + Class entityClass, + DataSource dataSource, + EntityFactory factory, + TryFunction fcn) + throws SourceException { + return unpack(buildEntityData(entityClass, dataSource, fcn).map(factory::get), entityClass); + } + + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + /** * Returns a stream of {@link EntityData} that can be used to build instances of several subtypes * of {@link Entity} by a corresponding {@link EntityFactory} that consumes this data. @@ -126,89 +170,132 @@ protected static Stream> buildEntityData( * @param type of entity data */ protected static Stream> buildEntityData( - Class entityClass, - DataSource dataSource, - Function, Try> fcn) { + Class entityClass, DataSource dataSource, TryFunction fcn) { return buildEntityData(entityClass, dataSource).map(fcn); } + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + /** - * Universal method to get a map: uuid to {@link UniqueEntity}. + * Method to build an enrich function. * - * @param entityClass subclass of {@link UniqueEntity} - * @param dataSource source for the data - * @param factory to build the entity - * @return a map: uuid to {@link UniqueEntity} - * @param type of entity - * @throws SourceException - if an error happen during reading + * @param fieldName name of the field + * @param entities map: uuid to {@link Entity} + * @param defaultEntity entity that should be used if no other entity was extracted + * @param fcn to build the returned {@link EntityData} + * @return a new entity data + * @param type of entity data + * @param type of entity + * @param type of returned entity data */ - @SuppressWarnings("unchecked") - protected static Map getEntities( - Class entityClass, - DataSource dataSource, - EntityFactory factory) - throws SourceException { - return unpackMap( - buildEntityData(entityClass, dataSource) - .map(data -> (Try) factory.get(data)), - entityClass); + protected static + TryFunction enrichWithDefault( + String fieldName, Map entities, T defaultEntity, BiFunction fcn) { + return entityData -> + entityData + .zip( + extract(entityData, fieldName, entities) + .orElse(() -> Try.Success.of(defaultEntity))) + .map(builder(List.of(fieldName), fcn)); } /** - * Universal method to get a {@link Entity} stream. + * Method to build an enrich function. * - * @param entityClass class of the entity - * @param dataSource source for the entity - * @param factory to build the entity - * @param fcn function to enrich the given entity data - * @return a set of {@link Entity}s - * @param type of entity - * @param type of entity data - * @throws SourceException - if an error happen during reading + * @param fieldName name of the field + * @param entities map: uuid to {@link Entity} + * @param fcn to build the returned {@link EntityData} + * @return a new entity data + * @param type of entity data + * @param type of entity + * @param type of returned entity data */ - protected static Stream getEntities( - Class entityClass, - DataSource dataSource, - EntityFactory factory, - Function, Try> fcn) - throws SourceException { - return unpack(buildEntityData(entityClass, dataSource, fcn).map(factory::get), entityClass); + protected static TryFunction enrich( + String fieldName, Map entities, BiFunction fcn) { + return entityData -> + entityData + .zip(extract(entityData, fieldName, entities)) + .map(builder(List.of(fieldName), fcn)); } - protected static Map unpackMap( - Stream> inputStream, Class entityClass) throws SourceException { - return unpack(inputStream, entityClass).collect(toMap()); + /** + * Method to build an enrich function. + * + * @param fieldName1 name of the first field + * @param entities1 map: uuid to {@link Entity} + * @param fieldName2 name of the second field + * @param entities2 map: uuid to {@link Entity} + * @param fcn to build the returned {@link EntityData} + * @return a new entity data + * @param type of entity data + * @param type of the first entity + * @param type of the second entity + * @param type of returned entity data + */ + protected static < + E extends EntityData, T1 extends Entity, T2 extends Entity, R extends EntityData> + TryFunction biEnrich( + String fieldName1, + Map entities1, + String fieldName2, + Map entities2, + TriFunction fcn) { + return entityData -> + entityData + .zip( + extract(entityData, fieldName1, entities1) + .zip(extract(entityData, fieldName2, entities2))) + .map( + builder( + List.of(fieldName1, fieldName2), + (data, pair) -> fcn.apply(data, pair.getKey(), pair.getValue()))); } - protected static Stream unpack( - Stream> inputStream, Class clazz) throws SourceException { - return Try.scanStream(inputStream, clazz.getSimpleName()) - .transformF(SourceException::new) - .getOrThrow(); + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= + /** + * Method to build an {@link EntityData}. + * + * @param fieldNames list with field names + * @param fcn to build the returned {@link EntityData} + * @return an entity data + * @param type of given entity data + * @param type of entities + * @param type of returned entity data + */ + private static Function, R> builder( + List fieldNames, BiFunction fcn) { + return pair -> { + E data = pair.getKey(); + T entities = pair.getValue(); + + Map fieldsToAttributes = data.getFieldsToValues(); + + // remove fields that are passed as objects to constructor + fieldNames.forEach(fieldsToAttributes.keySet()::remove); + + return fcn.apply(data, entities); + }; } /** - * Method to build an {@link Enrichment}. + * Method to unpack a stream of tries. * - * @param entityData data containing complex entities - * @param fieldName name of the field - * @param entities map: uuid to {@link Entity} - * @param defaultEntity entity to use if no other entity was found - * @return an enrichment with fallback value - * @param type of entity data - * @param type of entity + * @param inputStream given stream + * @param clazz class of the entity + * @return a stream of entities + * @param type of entity + * @param type of exception + * @throws SourceException - if an error occurred during reading */ - protected static - Enrichment buildEnrichmentWithDefault( - Try entityData, - String fieldName, - Map entities, - R defaultEntity) { - return buildEnrichment(entityData, fieldName, entities).orDefault(defaultEntity); + protected static Stream unpack( + Stream> inputStream, Class clazz) throws SourceException { + return Try.scanStream(inputStream, clazz.getSimpleName()) + .transformF(SourceException::new) + .getOrThrow(); } /** - * Method to build an {@link Enrichment}. + * Method to extract an entity. * * @param entityData data containing complex entities * @param fieldName name of the field @@ -217,23 +304,21 @@ Enrichment buildEnrichmentWithDefault( * @param type of entity data * @param type of entity */ - protected static Enrichment buildEnrichment( + protected static Try extract( Try entityData, String fieldName, Map entities) { - return new Enrichment<>( - fieldName, - entityData.flatMap( - data -> - Try.of(() -> data.getUUID(fieldName), FactoryException.class) - .transformF( - exception -> - new SourceException( - "Extracting UUID field " - + fieldName - + " from entity data " - + entityData - + " failed.", - exception)) - .flatMap(entityUuid -> getEntity(entityUuid, entities)))); + return entityData.flatMap( + data -> + Try.of(() -> data.getUUID(fieldName), FactoryException.class) + .transformF( + exception -> + new SourceException( + "Extracting UUID field " + + fieldName + + " from entity data " + + entityData + + " failed.", + exception)) + .flatMap(entityUuid -> extract(entityUuid, entities))); } /** @@ -244,7 +329,7 @@ protected static Enrichment buildEnr * @return a try of the {@link Entity} * @param type of entity */ - protected static Try getEntity(UUID uuid, Map entityMap) { + protected static Try extract(UUID uuid, Map entityMap) { return Optional.ofNullable(entityMap.get(uuid)) // We either find a matching entity for given UUID, thus return a success .map(entity -> Try.of(() -> entity, SourceException.class)) @@ -253,73 +338,19 @@ protected static Try getEntity(UUID uuid, Map e new Failure<>(new SourceException("Entity with uuid " + uuid + " was not provided."))); } - /** - * Method to enrich an {@link EntityData} with an entities. Mostly used with {@link - * EnrichFunction}. - * - * @param entityData to enrich - * @param enrichment for enriching - * @param fcn to build the returned {@link EntityData} - * @return a new entity data - * @param type of entity data - * @param type of entity - * @param type of returned entity data - */ - protected static - Try enrich( - Try entityData, Enrichment enrichment, BiFunction fcn) { - return entityData.flatMap( - data -> - enrichment.map( - (fieldName, entity) -> { - Map fieldsToAttributes = data.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(fieldName); + // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= - return fcn.apply(data, entity); - })); - } + // functional interfaces /** - * Method to enrich an {@link EntityData} with two entities. Mostly used with {@link - * BiEnrichFunction}. + * Adapts the function arguments to a try. * - * @param entityData to enrich - * @param enrichment1 first enrichment - * @param enrichment2 second enrichment - * @param fcn to build the returned {@link EntityData} - * @return a new entity data - * @param type of entity data - * @param type of first entity - * @param type of second entity - * @param type of returned entity data + * @param type of first argument + * @param type of second argument */ - protected static < - E extends EntityData, T1 extends Entity, T2 extends Entity, R extends EntityData> - Try biEnrich( - Try entityData, - Enrichment enrichment1, - Enrichment enrichment2, - TriFunction fcn) { - return entityData.flatMap( - data -> - enrichment1 - .entity - .zip(enrichment2.entity) - .map( - zippedData -> { - Map fieldsToAttributes = data.getFieldsToValues(); - - // remove fields that are passed as objects to constructor - fieldsToAttributes.keySet().remove(enrichment1.fieldName); - fieldsToAttributes.keySet().remove(enrichment2.fieldName); - - return fcn.apply(data, zippedData.getKey(), zippedData.getValue()); - })); - } - - // functional interfaces + @FunctionalInterface + protected interface TryFunction + extends Function, Try> {} /** * Function for enriching an {@link EntityData} with an {@link Entity}. @@ -368,35 +399,4 @@ protected interface TriEnrichFunction< Map, Map, Try> {} - - /** - * Container class for enriching an {@link EntityData}. - * - * @param fieldName name of the field - * @param entity try of the entity - * @param type of the entity - */ - protected record Enrichment(String fieldName, Try entity) { - - /** - * Replaces a {@link Failure} with the given entity - * - * @param defaultEntity given entity - * @return a new {@link Enrichment} - */ - public Enrichment orDefault(T defaultEntity) { - return new Enrichment<>(fieldName, entity.orElse(() -> Try.Success.of(defaultEntity))); - } - - /** - * Method to map the entity while also using the field name. - * - * @param mapper function - * @return a new {@link Try} - * @param type of entity - */ - public Try map(BiFunction mapper) { - return entity.map(data -> mapper.apply(fieldName, data)); - } - } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java index 52a77d40c..c2ca71510 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java @@ -99,7 +99,7 @@ public GraphicElements getGraphicElements(Map nodes, Map getNodeGraphicInput() throws SourceException { public Set getNodeGraphicInput(Map nodes) throws SourceException { - return toSet( - getEntities( + return getEntities( NodeGraphicInput.class, dataSource, nodeGraphicInputFactory, - data -> - enrich(data, buildEnrichment(data, NODE, nodes), NodeGraphicInputEntityData::new))); + enrich(NODE, nodes, NodeGraphicInputEntityData::new)) + .collect(toSet()); } /** @@ -143,13 +142,11 @@ public Set getLineGraphicInput() throws SourceException { public Set getLineGraphicInput(Map lines) throws SourceException { - return toSet( - getEntities( + return getEntities( LineGraphicInput.class, dataSource, lineGraphicInputFactory, - data -> - enrich( - data, buildEnrichment(data, "line", lines), LineGraphicInputEntityData::new))); + enrich("line", lines, LineGraphicInputEntityData::new)) + .collect(toSet()); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java index b95ef6d33..4ff8a8c0d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java @@ -16,8 +16,6 @@ import edu.ie3.datamodel.models.input.container.RawGridElements; import edu.ie3.datamodel.utils.Try; import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; import java.util.stream.Stream; /** @@ -300,7 +298,7 @@ public Set get2WTransformers( operators, nodes, transformer2WTypes) - .collect(Collectors.toSet()); + .collect(toSet()); } /** @@ -344,22 +342,20 @@ public Set get3WTransformers( Map nodes, Map transformer3WTypes) throws SourceException { - - Function, Try> - builder = - data -> - connectorEnricher - .andThen( - enrichedData -> - biEnrich( - enrichedData, - buildEnrichment(enrichedData, "nodeC", nodes), - buildEnrichment(enrichedData, TYPE, transformer3WTypes), - Transformer3WInputEntityData::new)) - .apply(data, operators, nodes); + TryFunction builder = + data -> + connectorEnricher + .andThen( + biEnrich( + "nodeC", + nodes, + TYPE, + transformer3WTypes, + Transformer3WInputEntityData::new)) + .apply(data, operators, nodes); return getEntities(Transformer3WInput.class, dataSource, transformer3WInputFactory, builder) - .collect(Collectors.toSet()); + .collect(toSet()); } /** @@ -402,7 +398,7 @@ public Set getSwitches( dataSource, switchInputFactory, data -> connectorEnricher.apply(data, operators, nodes)) - .collect(Collectors.toSet()); + .collect(toSet()); } /** @@ -446,6 +442,6 @@ public Set getMeasurementUnits( dataSource, measurementUnitInputFactory, data -> nodeAssetEnricher.apply(data, operators, nodes)) - .collect(Collectors.toSet()); + .collect(toSet()); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java index 30656ec88..b2ff0843e 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java @@ -23,7 +23,6 @@ import java.util.ArrayList; import java.util.List; import java.util.Set; -import java.util.function.Function; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -374,8 +373,7 @@ public Set getEmResults() throws SourceException { private Set getResultEntities( Class entityClass, ResultEntityFactory factory) throws SourceException { - return getEntities( - entityClass, dataSource, (ResultEntityFactory) factory, Function.identity()) + return getEntities(entityClass, dataSource, (ResultEntityFactory) factory, t -> t) .collect(Collectors.toSet()); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index ede81d525..dda0b82b8 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -10,6 +10,7 @@ import edu.ie3.datamodel.exceptions.SystemParticipantsException; import edu.ie3.datamodel.exceptions.ValidationException; import edu.ie3.datamodel.io.factory.EntityData; +import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData; import edu.ie3.datamodel.io.factory.input.participant.*; import edu.ie3.datamodel.models.input.EmInput; import edu.ie3.datamodel.models.input.NodeInput; @@ -24,7 +25,7 @@ import java.util.Map; import java.util.Set; import java.util.UUID; -import java.util.function.Function; +import java.util.function.BiFunction; import java.util.stream.Stream; /** @@ -59,15 +60,15 @@ public class SystemParticipantSource extends AssetEntitySource { EntityData, OperatorInput, NodeInput, EmInput, SystemParticipantEntityData> participantEnricher = (data, operators, nodes, emUnits) -> - nodeAssetEnricher + assetEnricher + .andThen(enrich(NODE, nodes, NodeAssetInputEntityData::new)) .andThen( - enrichedData -> - enrich( - enrichedData, - buildEnrichment( - enrichedData, SystemParticipantInputEntityFactory.EM, emUnits), - SystemParticipantEntityData::new)) - .apply(data, operators, nodes); + enrichWithDefault( + SystemParticipantInputEntityFactory.EM, + emUnits, + null, + SystemParticipantEntityData::new)) + .apply(data, operators); public SystemParticipantSource( TypeSource typeSource, @@ -283,12 +284,12 @@ public Set getFixedFeedIns() throws SourceException { public Set getFixedFeedIns( Map operators, Map nodes, Map emUnits) throws SourceException { - return toSet( - getEntities( + return getEntities( FixedFeedInInput.class, dataSource, fixedFeedInInputFactory, - data -> participantEnricher.apply(data, operators, nodes, emUnits))); + data -> participantEnricher.apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -328,12 +329,12 @@ public Set getPvPlants() throws SourceException { public Set getPvPlants( Map operators, Map nodes, Map emUnits) throws SourceException { - return toSet( - getEntities( + return getEntities( PvInput.class, dataSource, pvInputFactory, - data -> participantEnricher.apply(data, operators, nodes, emUnits))); + data -> participantEnricher.apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -373,12 +374,12 @@ public Set getLoads() throws SourceException { public Set getLoads( Map operators, Map nodes, Map emUnits) throws SourceException { - return toSet( - getEntities( + return getEntities( LoadInput.class, dataSource, loadInputFactory, - data -> participantEnricher.apply(data, operators, nodes, emUnits))); + data -> participantEnricher.apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -418,12 +419,12 @@ public Set getEvcs() throws SourceException { public Set getEvcs( Map operators, Map nodes, Map emUnits) throws SourceException { - return toSet( - getEntities( + return getEntities( EvcsInput.class, dataSource, evcsInputFactory, - data -> participantEnricher.apply(data, operators, nodes, emUnits))); + data -> participantEnricher.apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -468,12 +469,13 @@ public Set getBmPlants( Map emUnits, Map types) throws SourceException { - return toSet( - getEntities( + return getEntities( BmInput.class, dataSource, bmInputFactory, - data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); + data -> + participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -520,12 +522,13 @@ public Set getStorages( Map emUnits, Map types) throws SourceException { - return toSet( - getEntities( + return getEntities( StorageInput.class, dataSource, storageInputFactory, - data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); + data -> + participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -570,12 +573,13 @@ public Set getWecPlants( Map emUnits, Map types) throws SourceException { - return toSet( - getEntities( + return getEntities( WecInput.class, dataSource, wecInputFactory, - data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); + data -> + participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + .collect(toSet()); } /** @@ -619,12 +623,13 @@ public Set getEvs( Map emUnits, Map types) throws SourceException { - return toSet( - getEntities( + return getEntities( EvInput.class, dataSource, evInputFactory, - data -> enrich(participantEnricher.apply(data, operators, nodes, emUnits), types))); + data -> + participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + .collect(toSet()); } public Set getChpPlants() throws SourceException { @@ -668,20 +673,20 @@ public Set getChpPlants( Map thermalStorages) throws SourceException { - Function, Try> builder = + TryFunction builder = data -> participantEnricher - .andThen(participantData -> enrich(participantData, types)) + .andThen(enrich(types)) .andThen( - typedData -> - biEnrich( - typedData, - buildEnrichment(typedData, THERMAL_BUS, thermalBuses), - buildEnrichment(typedData, THERMAL_STORAGE, thermalStorages), - ChpInputEntityData::new)) + biEnrich( + THERMAL_BUS, + thermalBuses, + THERMAL_STORAGE, + thermalStorages, + ChpInputEntityData::new)) .apply(data, operators, nodes, emUnits); - return toSet(getEntities(ChpInput.class, dataSource, chpInputFactory, builder)); + return getEntities(ChpInput.class, dataSource, chpInputFactory, builder).collect(toSet()); } public Set getHeatPumps() throws SourceException { @@ -720,33 +725,28 @@ public Set getHeatPumps( Map thermalBuses) throws SourceException { - Function, Try> builder = + TryFunction builder = data -> participantEnricher - .andThen(participantData -> enrich(participantData, types)) - .andThen( - typedData -> - enrich( - typedData, - buildEnrichment(typedData, THERMAL_BUS, thermalBuses), - HpInputEntityData::new)) + .andThen(enrich(types)) + .andThen(enrich(THERMAL_BUS, thermalBuses, HpInputEntityData::new)) .apply(data, operators, nodes, emUnits); - return toSet(getEntities(HpInput.class, dataSource, hpInputFactory, builder)); + return getEntities(HpInput.class, dataSource, hpInputFactory, builder).collect(toSet()); } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=- /** - * Method for enriching {@link SystemParticipantEntityData} with types. + * Builds a function for enriching {@link SystemParticipantEntityData} with types. * - * @param data to enrich * @param types all known types * @return a typed entity data * @param type of types */ private static - Try, SourceException> enrich( - Try data, Map types) { - return enrich(data, buildEnrichment(data, TYPE, types), SystemParticipantTypedEntityData::new); + TryFunction> enrich(Map types) { + BiFunction> fcn = + SystemParticipantTypedEntityData::new; + return entityData -> enrich(TYPE, types, fcn).apply(entityData); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java index c9c76d274..f3334600d 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ThermalSource.java @@ -42,12 +42,7 @@ public class ThermalSource extends AssetEntitySource { thermalUnitEnricher = (data, operators, buses) -> assetEnricher - .andThen( - assetData -> - enrich( - assetData, - buildEnrichment(assetData, "thermalbus", buses), - ThermalUnitInputEntityData::new)) + .andThen(enrich("thermalbus", buses, ThermalUnitInputEntityData::new)) .apply(data, operators); public ThermalSource(TypeSource typeSource, DataSource dataSource) { @@ -242,11 +237,11 @@ public Set getCylindricalStorages() throws SourceExcept public Set getCylindricalStorages( Map operators, Map thermalBuses) throws SourceException { - return toSet( - getEntities( + return getEntities( CylindricalStorageInput.class, dataSource, cylindricalStorageInputFactory, - data -> thermalUnitEnricher.apply(data, operators, thermalBuses))); + data -> thermalUnitEnricher.apply(data, operators, thermalBuses)) + .collect(toSet()); } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy index 4a2d57c81..5002c9117 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy @@ -7,226 +7,106 @@ package edu.ie3.datamodel.io.source import static edu.ie3.test.helper.EntityMap.map -import edu.ie3.datamodel.exceptions.SourceException -import edu.ie3.datamodel.exceptions.ValidationException import edu.ie3.datamodel.io.factory.EntityData -import edu.ie3.datamodel.io.factory.input.AssetInputEntityData -import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData -import edu.ie3.datamodel.io.factory.input.participant.ChpInputEntityData -import edu.ie3.datamodel.io.factory.input.participant.SystemParticipantTypedEntityData -import edu.ie3.datamodel.io.source.csv.CsvDataSource -import edu.ie3.datamodel.models.input.AssetInput -import edu.ie3.datamodel.models.input.EmInput -import edu.ie3.datamodel.models.input.NodeInput -import edu.ie3.datamodel.models.input.system.ChpInput -import edu.ie3.datamodel.models.input.system.type.ChpTypeInput -import edu.ie3.datamodel.models.input.thermal.ThermalBusInput -import edu.ie3.datamodel.models.input.thermal.ThermalStorageInput +import edu.ie3.datamodel.io.factory.input.ConnectorInputEntityData +import edu.ie3.datamodel.io.factory.input.LineInputFactory +import edu.ie3.datamodel.models.input.OperatorInput +import edu.ie3.datamodel.models.input.connector.LineInput import edu.ie3.datamodel.utils.Try import edu.ie3.test.common.GridTestData -import edu.ie3.test.common.SystemParticipantTestData as sptd -import spock.lang.Shared import spock.lang.Specification class AssetEntitySourceTest extends Specification { - private final class DummyEntitySource extends AssetEntitySource { - DummyEntitySource(CsvDataSource dataSource) { - super(dataSource) - } - - @Override - void validate() throws ValidationException { - } - } - - @Shared - DummyEntitySource dummyEntitySource = new DummyEntitySource(Mock(CsvDataSource)) - - def "An EntitySource should enrich entity data with a linked entity, if it was provided"() { - given: - Map parameter = [ - "linked_entity" : GridTestData.nodeA.uuid.toString(), - ] - def entityData = new AssetInputEntityData(parameter, AssetInput) - - Map entityMap = map([GridTestData.nodeA]) - - when: - def result = dummyEntitySource.enrichEntityData(entityData, "linked_entity", entityMap, NodeAssetInputEntityData::new) - - then: - result == new Try.Success(new NodeAssetInputEntityData(entityData, GridTestData.nodeA)) - } - - def "An EntitySource trying to enrich entity data should fail, if no matching linked entity was provided"() { - given: - Map parameter = [ - "linked_entity" : GridTestData.nodeB.uuid.toString(), - ] - def entityData = new AssetInputEntityData(parameter, AssetInput) - - Map entityMap = map([GridTestData.nodeA]) - - when: - def result = dummyEntitySource.enrichEntityData(entityData, "linked_entity", entityMap, NodeAssetInputEntityData::new) - - then: - result.isFailure() - result.getException().get().message.startsWith("Linked linked_entity with UUID 47d29df0-ba2d-4d23-8e75-c82229c5c758 was not found for entity AssetInputEntityData") - } - - def "An EntitySource should enrich entity data with two linked entities, if they are provided"() { - given: - Map parameter = [ - "t_bus" : sptd.thermalBus.uuid.toString(), - "t_storage" : sptd.thermalStorage.uuid.toString() - ] - def entityData = new SystemParticipantTypedEntityData(parameter, ChpInput, sptd.participantNode, null, sptd.chpTypeInput) - - Map busMap = map([sptd.thermalBus]) - Map storageMap = map([sptd.thermalStorage]) - - when: - def result = dummyEntitySource.enrichEntityData(entityData, "t_bus", busMap, "t_storage", storageMap, ChpInputEntityData::new) - - then: - result == new Try.Success(new ChpInputEntityData(entityData, sptd.thermalBus, sptd.thermalStorage)) - } - - def "An EntitySource trying to enrich entity data should fail, if one of two linked entities is not provided"() { - given: - Map parameter = [ - "t_bus" : sptd.thermalBus.uuid.toString(), - "t_storage" : "8851813b-3a7d-4fee-874b-4df9d724e4b4" - ] - def entityData = new SystemParticipantTypedEntityData(parameter, ChpInput, sptd.participantNode, null, sptd.chpTypeInput) - Map busMap = map([sptd.thermalBus]) - Map storageMap = map([sptd.thermalStorage]) - - when: - def result = dummyEntitySource.enrichEntityData(entityData, "t_bus", busMap, "t_storage", storageMap, ChpInputEntityData::new) - - then: - result.isFailure() - result.getException().get().message.startsWith("Linked t_storage with UUID 8851813b-3a7d-4fee-874b-4df9d724e4b4 was not found for entity SystemParticipantTypedEntityData") - } - - def "An EntitySource should find a linked entity, if it was provided"() { + def "An AssetEntitySource assetEnricher should work as expected"() { given: - Map parameter = [ - "linked_entity" : sptd.emInput.uuid.toString(), - ] - def entityData = new EntityData(parameter, AssetInput) - - Map entityMap = map([sptd.emInput]) + def entityData = new EntityData(["operator": ""], LineInput) + def operators = map([GridTestData.profBroccoli]) when: - def result = dummyEntitySource.getLinkedEntity(entityData, "linked_entity", entityMap) + def actual = AssetEntitySource.assetEnricher.apply(new Try.Success<>(entityData), operators) then: - result == new Try.Success(sptd.emInput) + actual.success + actual.data.get().operatorInput == OperatorInput.NO_OPERATOR_ASSIGNED } - def "An EntitySource trying to find a linked entity should fail, if no matching linked entity was provided"() { + def "An AssetEntitySource nodeAssetEnricher should work as expected"() { given: - Map parameter = [ - "linked_entity" : sptd.parentEm.uuid.toString(), - ] - def entityData = new EntityData(parameter, AssetInput) - - Map entityMap = map([sptd.emInput]) + def entityData = new EntityData(["operator": "", "node": GridTestData.nodeA.uuid.toString()], LineInput) + def operators = map([GridTestData.profBroccoli]) + def nodes = map([GridTestData.nodeA, GridTestData.nodeB]) when: - def result = dummyEntitySource.getLinkedEntity(entityData, "linked_entity", entityMap) + def actual = AssetEntitySource.nodeAssetEnricher.apply(new Try.Success<>(entityData), operators, nodes) then: - result.isFailure() - result.getException().get().message == "Linked linked_entity with UUID 897bfc17-8e54-43d0-8d98-740786fd94dd was not found for entity EntityData{fieldsToAttributes={linked_entity=897bfc17-8e54-43d0-8d98-740786fd94dd}, targetClass=class edu.ie3.datamodel.models.input.AssetInput}" + actual.success + actual.data.get().node == GridTestData.nodeA } - def "An EntitySource trying to find a linked entity should fail, if corresponding UUID is malformed"() { + def "An AssetEntitySource connectorEnricher should work as expected"() { given: - Map parameter = [ - "linked_entity" : "not-a-uuid", - ] - def entityData = new EntityData(parameter, AssetInput) - - Map entityMap = map([sptd.emInput]) + def entityData = new EntityData(["operator": "", "nodeA": GridTestData.nodeA.uuid.toString(), "nodeB": GridTestData.nodeB.uuid.toString()], LineInput) + def operators = map([GridTestData.profBroccoli]) + def nodes = map([GridTestData.nodeA, GridTestData.nodeB]) when: - def result = dummyEntitySource.getLinkedEntity(entityData, "linked_entity", entityMap) + def actual = AssetEntitySource.connectorEnricher.apply(new Try.Success<>(entityData), operators, nodes) then: - result.isFailure() - result.getException().get().message == "Extracting UUID field linked_entity from entity data EntityData{fieldsToAttributes={linked_entity=not-a-uuid}, targetClass=class edu.ie3.datamodel.models.input.AssetInput} failed." + actual.success + actual.data.get().nodeA == GridTestData.nodeA + actual.data.get().nodeB == GridTestData.nodeB } - def "An EntitySource should optionally enrich entity data with a linked entity, if it was provided"() { + def "An AssetEntitySource can return a stream of typed connector entities"() { given: - Map parameter = [ - "linked_entity" : GridTestData.nodeA.uuid.toString(), + def parameters = [ + "uuid": "92ec3bcf-1777-4d38-af67-0bf7c9fa73c7", + "id": "test_line_AtoB", + "operator": GridTestData.profBroccoli.uuid.toString(), + "parallelDevices": "2", + "length": "0.003", + "geoPosition": "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", + "nodeA": GridTestData.nodeA.uuid.toString(), + "nodeB": GridTestData.nodeB.uuid.toString(), + "type": GridTestData.lineTypeInputCtoD.uuid.toString() ] - def entityData = new AssetInputEntityData(parameter, AssetInput) - - Map entityMap = map([GridTestData.nodeA]) + def source = DummyDataSource.of(parameters) + def operators = map([GridTestData.profBroccoli]) + def nodes = map([ + GridTestData.nodeA, + GridTestData.nodeB + ]) + def types = map([ + GridTestData.lineTypeInputCtoD + ]) when: - def result = dummyEntitySource.optionallyEnrichEntityData(entityData, "linked_entity", entityMap, GridTestData.nodeB, NodeAssetInputEntityData::new) + def actual = AssetEntitySource.getTypedConnectorEntities(LineInput, source, new LineInputFactory(), operators, nodes, types).toList() then: - result == new Try.Success(new NodeAssetInputEntityData(entityData, GridTestData.nodeA)) - } - - def "An EntitySource should (optionally) enrich entity data with the default entity, if no linked entity is specified"() { - given: - Map parameter = [ - "linked_entity" : "", - ] - def entityData = new AssetInputEntityData(parameter, AssetInput) - - Map entityMap = map([GridTestData.nodeA]) - - when: - def result = dummyEntitySource.optionallyEnrichEntityData(entityData, "linked_entity", entityMap, GridTestData.nodeB, NodeAssetInputEntityData::new) - - then: - result == new Try.Success(new NodeAssetInputEntityData(entityData, GridTestData.nodeB)) - } - - def "An EntitySource trying to optionally find a linked entity should fail, if no matching linked entity was provided"() { - given: - Map parameter = [ - "linked_entity" : "4ca90220-74c2-4369-9afa-a18bf068840e", - ] - def entityData = new AssetInputEntityData(parameter, AssetInput) - - Map entityMap = map([GridTestData.nodeA]) - - when: - def result = dummyEntitySource.optionallyEnrichEntityData(entityData, "linked_entity", entityMap, GridTestData.nodeB, NodeAssetInputEntityData::new) - - then: - result.isFailure() - result.getException().get().message.startsWith("Linked linked_entity with UUID 4ca90220-74c2-4369-9afa-a18bf068840e was not found for entity AssetInputEntityData{fieldsToValues={linked_entity=4ca90220-74c2-4369-9afa-a18bf068840e}, targetClass=class edu.ie3.datamodel.models.input.AssetInput") + actual.size() == 1 + actual.get(0).with { + // we only want to test of enriching with nodes and type worked as expected + assert it.nodeA == GridTestData.nodeA + assert it.nodeB == GridTestData.nodeB + assert it.type == GridTestData.lineTypeInputCtoD + } } - - def "An EntitySource trying to optionally find a linked entity should fail, if corresponding UUID is malformed"() { + def "An AssetEntitySource can enrich ConnectorInputEntityData with AssetTypeInput correctly"() { given: - Map parameter = [ - "linked_entity" : "not-a-uuid", - ] - def entityData = new AssetInputEntityData(parameter, AssetInput) - - Map entityMap = map([GridTestData.nodeA]) + def entityData = new ConnectorInputEntityData(["type": GridTestData.lineTypeInputCtoD.uuid.toString()], LineInput, GridTestData.nodeA, GridTestData.nodeB) + def types = map([GridTestData.lineTypeInputCtoD]) when: - def result = dummyEntitySource.optionallyEnrichEntityData(entityData, "linked_entity", entityMap, GridTestData.nodeB, NodeAssetInputEntityData::new) + def actual = AssetEntitySource.enrich(types).apply(new Try.Success<>(entityData)) then: - result.isFailure() - result.getException().get().message == "Exception while trying to parse UUID of field \"linked_entity\" with value \"not-a-uuid\"" + actual.success + actual.data.get().type == GridTestData.lineTypeInputCtoD } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/DummyDataSource.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/DummyDataSource.groovy new file mode 100644 index 000000000..af07453c5 --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/io/source/DummyDataSource.groovy @@ -0,0 +1,34 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.io.source + +import edu.ie3.datamodel.exceptions.SourceException +import edu.ie3.datamodel.models.Entity + +import java.util.stream.Stream + +class DummyDataSource implements DataSource { + + private final Map data + + private DummyDataSource(Map data) { + this.data = data + } + + static DummyDataSource of(Map data) { + return new DummyDataSource(data) + } + + @Override + Optional> getSourceFields(Class entityClass) throws SourceException { + return Optional.empty() + } + + @Override + Stream> getSourceData(Class entityClass) throws SourceException { + return Stream.of(data) + } +} diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy new file mode 100644 index 000000000..90c8d84a8 --- /dev/null +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy @@ -0,0 +1,193 @@ +/* + * © 2024. TU Dortmund University, + * Institute of Energy Systems, Energy Efficiency and Energy Economics, + * Research group Distribution grid planning and operation + */ +package edu.ie3.datamodel.io.source + +import static edu.ie3.datamodel.io.source.EntitySource.* +import static edu.ie3.test.helper.EntityMap.map + +import edu.ie3.datamodel.exceptions.SourceException +import edu.ie3.datamodel.io.factory.EntityData +import edu.ie3.datamodel.io.factory.input.AssetInputEntityData +import edu.ie3.datamodel.io.factory.input.ConnectorInputEntityData +import edu.ie3.datamodel.io.factory.input.OperatorInputFactory +import edu.ie3.datamodel.models.input.NodeInput +import edu.ie3.datamodel.models.input.OperatorInput +import edu.ie3.datamodel.models.input.connector.LineInput +import edu.ie3.datamodel.utils.Try +import edu.ie3.datamodel.utils.validation.DummyAssetInput +import edu.ie3.test.common.GridTestData +import org.apache.commons.lang3.tuple.Pair +import spock.lang.Specification + +class EntitySourceTest extends Specification { + + def "An EntitySource can build a map of entities correctly"() { + given: + Map parameter = ["uuid": GridTestData.profBroccoli.uuid.toString(), "id": GridTestData.profBroccoli.id] + def source = DummyDataSource.of(parameter) + + when: + def actual = getEntities(OperatorInput, source, new OperatorInputFactory()) + + then: + actual.size() == 1 + OperatorInput input = actual.get(GridTestData.profBroccoli.uuid) + input.id == GridTestData.profBroccoli.id + } + + def "An EntitySource throws a SourceException if an entity can not be build"() { + given: + Map parameter = ["uuid": GridTestData.profBroccoli.uuid.toString()] + def source = DummyDataSource.of(parameter) + + when: + getEntities(OperatorInput, source, new OperatorInputFactory()) + + then: + SourceException ex = thrown() + ex.message == "edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"OperatorInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: An error occurred when creating instance of OperatorInput.class." + } + + def "An EntitySource can build EntityData correctly"() { + given: + Map parameter = ["operator": GridTestData.profBroccoli.uuid.toString()] + def source = DummyDataSource.of(parameter) + + when: + def actual = buildEntityData(DummyAssetInput, source).toList() + + then: + actual.size() == 1 + actual.get(0).success + } + + def "An EntitySource can enrich and build EntityData correctly"() { + given: + Map parameter = ["operator": GridTestData.profBroccoli.uuid.toString()] + def entityMap = map([GridTestData.profBroccoli]) + def source = DummyDataSource.of(parameter) + def fcn = enrich("operator", entityMap, AssetInputEntityData::new) + + when: + def actual = buildEntityData(DummyAssetInput, source, fcn).toList() + + then: + actual.size() == 1 + actual.get(0).success + def data = actual.get(0).data.get() + + data.targetClass == DummyAssetInput + data.fieldsToValues.size() == 0 + } + + def "An EntitySource can enrich EntityData with default fallback"() { + given: + def entityMap = map([GridTestData.profBroccoli]) + def entityData1 = new EntityData(["operator": GridTestData.profBroccoli.uuid.toString()], NodeInput) + def entityData2 = new EntityData(["operator": ""], NodeInput) + def fcn = enrichWithDefault("operator", entityMap, OperatorInput.NO_OPERATOR_ASSIGNED, AssetInputEntityData::new) + + when: + def enrichedWithEntity = fcn.apply(new Try.Success<>(entityData1)) + def enrichedWithDefault = fcn.apply(new Try.Success<>(entityData2)) + + then: + enrichedWithEntity.success + enrichedWithEntity.data.get().operatorInput == GridTestData.profBroccoli + + enrichedWithDefault.success + enrichedWithDefault.data.get().operatorInput == OperatorInput.NO_OPERATOR_ASSIGNED + } + + def "An EntitySource can enrich EntityData"() { + given: + def entityMap = map([GridTestData.profBroccoli]) + def entityData = new EntityData(["operator": GridTestData.profBroccoli.uuid.toString()], NodeInput) + def fcn = enrich("operator", entityMap, AssetInputEntityData::new) + + when: + def enrichedData = fcn.apply(new Try.Success<>(entityData)) + + then: + enrichedData.success + enrichedData.data.get().operatorInput == GridTestData.profBroccoli + } + + def "An EntitySource can enrich EntityData with two entities"() { + given: + def entityMap = map([GridTestData.nodeA, GridTestData.nodeB]) + def entityData = new AssetInputEntityData(["nodeA": GridTestData.nodeA.uuid.toString(), "nodeB": GridTestData.nodeB.uuid.toString()], LineInput) + def fcn = biEnrich("nodeA", entityMap, "nodeB", entityMap, ConnectorInputEntityData::new) + + when: + def enrichedData = fcn.apply(new Try.Success<>(entityData)) + + then: + enrichedData.success + enrichedData.data.get().nodeA == GridTestData.nodeA + enrichedData.data.get().nodeB == GridTestData.nodeB + } + + def "An EntitySource's builder function should work as expected"() { + given: + def entityData = new EntityData(["operator": ""], NodeInput) + def pair = Pair.of(entityData, GridTestData.profBroccoli) + def fcn = builder(["operator"], AssetInputEntityData::new) + + when: + def result = fcn.apply(pair) + + then: + result.fieldsToValues.isEmpty() + result.operatorInput == GridTestData.profBroccoli + } + + def "An EntitySource can extract an Entity from a map correctly if a field name is given"() { + given: + def entityData = new EntityData(["operator": GridTestData.profBroccoli.uuid.toString()], NodeInput) + def entityMap = map([GridTestData.profBroccoli]) + + when: + def actual = extract(new Try.Success<>(entityData), "operator", entityMap) + + then: + actual.success + actual.data.get() == GridTestData.profBroccoli + } + + def "An EntitySource returns a failure if an entity can not be extracted from a given map"() { + given: + def entityData = new EntityData(fieldsToAttributes, NodeInput) + + when: + def actual = extract(new Try.Success<>(entityData), "operator", entityMap) + + then: + actual.failure + actual.exception.get().class == SourceException + actual.exception.get().message.contains(expectedMessage) + + where: + fieldsToAttributes | entityMap | expectedMessage + ["operator": "no uuid"] | map([OperatorInput.NO_OPERATOR_ASSIGNED]) | "Extracting UUID field operator from entity data" + ["operator": GridTestData.profBroccoli.uuid.toString()] | map([OperatorInput.NO_OPERATOR_ASSIGNED]) | "Entity with uuid f15105c4-a2de-4ab8-a621-4bc98e372d92 was not provided." + } + + def "An EntitySource returns a failure if a given map does not contain the given uuid"() { + given: + def uuid = GridTestData.profBroccoli.uuid + def entityMap = map([ + OperatorInput.NO_OPERATOR_ASSIGNED + ]) + + when: + def actual = extract(uuid, entityMap) + + then: + actual.failure + actual.exception.get().message == "Entity with uuid f15105c4-a2de-4ab8-a621-4bc98e372d92 was not provided." + } +} diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy index 57e7a5566..7d9be93e3 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy @@ -7,115 +7,45 @@ package edu.ie3.datamodel.io.source import static edu.ie3.test.helper.EntityMap.map -import edu.ie3.datamodel.io.factory.input.NodeAssetInputEntityData -import edu.ie3.datamodel.io.factory.input.participant.ChpInputEntityData -import edu.ie3.datamodel.io.factory.input.participant.HpInputEntityData +import edu.ie3.datamodel.io.factory.input.ConnectorInputEntityData import edu.ie3.datamodel.io.factory.input.participant.SystemParticipantEntityData -import edu.ie3.datamodel.io.factory.input.participant.SystemParticipantTypedEntityData -import edu.ie3.datamodel.models.input.system.ChpInput -import edu.ie3.datamodel.models.input.system.HpInput -import edu.ie3.datamodel.models.input.thermal.ThermalBusInput -import edu.ie3.datamodel.models.input.thermal.ThermalStorageInput +import edu.ie3.datamodel.models.input.OperatorInput +import edu.ie3.datamodel.models.input.connector.LineInput +import edu.ie3.datamodel.models.input.system.EvInput import edu.ie3.datamodel.utils.Try -import edu.ie3.test.common.SystemParticipantTestData +import edu.ie3.test.common.GridTestData +import edu.ie3.test.common.SystemParticipantTestData as sptd import spock.lang.Specification -import java.util.stream.Stream - class SystemParticipantSourceTest extends Specification { - def "A SystemParticipantSource should build system participant entity from valid and invalid input data as expected"() { - given: - def nodeAssetInputEntityData = Stream.of(new Try.Success<>(new NodeAssetInputEntityData(fieldsToAttributes, ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node))) - - when: - def sysPartEntityDataStream = SystemParticipantSource.systemParticipantEntityStream(nodeAssetInputEntityData, map(emUnits)) - - then: - def element = sysPartEntityDataStream.findFirst().get() - element.success == resultIsPresent - element.data.ifPresent({ - typedEntityData -> - assert (typedEntityData == resultData) - }) - - where: - emUnits | fieldsToAttributes || resultIsPresent || resultData - [] | ["em": "977157f4-25e5-4c72-bf34-440edc778792"] || false || null - [SystemParticipantTestData.emInput] | ["bla": "foo"] || true || new SystemParticipantEntityData(["bla": "foo"], ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, null) - [SystemParticipantTestData.emInput] | [:] || true || new SystemParticipantEntityData([:], ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, null) - [SystemParticipantTestData.emInput] | ["em": "977157f4-25e5-4c72-bf34-440edc778793"] || false || null - [SystemParticipantTestData.emInput] | ["em": "977157f4-25e5-4c72-bf34-440edc778792"] || true || new SystemParticipantEntityData([:], ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, SystemParticipantTestData.emInput) - } - - def "A SystemParticipantSource should build typed entity from valid and invalid input data as expected"() { + def "An SystemParticipantSource participantEnricher should work as expected"() { given: - def systemParticipantEntityData = Stream.of(new Try.Success<>(new SystemParticipantEntityData(fieldsToAttributes, ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, null))) + def entityData = new ConnectorInputEntityData(["operators": "", "node": sptd.participantNode.uuid.toString(), "em": sptd.emInput.uuid.toString()], LineInput, GridTestData.nodeA, GridTestData.nodeB) + def operators = map([OperatorInput.NO_OPERATOR_ASSIGNED]) + def nodes = map([sptd.participantNode]) + def emUnits = map([sptd.emInput]) when: - def typedEntityDataStream = SystemParticipantSource.typedSystemParticipantEntityStream(systemParticipantEntityData, map(types)) + def actual = SystemParticipantSource.participantEnricher.apply(new Try.Success<>(entityData), operators, nodes, emUnits) then: - def element = typedEntityDataStream.findFirst().get() - element.success == resultIsPresent - element.data.ifPresent({ - typedEntityData -> - assert (typedEntityData == resultData) - }) - - where: - types | fieldsToAttributes || resultIsPresent || resultData - [] | ["type": "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8"] || false || null - [SystemParticipantTestData.chpTypeInput] | ["bla": "foo"] || false || null - [SystemParticipantTestData.chpTypeInput] | [:] || false || null - [SystemParticipantTestData.chpTypeInput] | ["type": "5ebd8f7e-dedb-4017-bb86-6373c4b68eb9"] || false || null - [SystemParticipantTestData.chpTypeInput] | ["type": "5ebd8f7e-dedb-4017-bb86-6373c4b68eb8"] || true || new SystemParticipantTypedEntityData<>([:], ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, null, SystemParticipantTestData.chpTypeInput) + actual.success + actual.data.get().operatorInput == OperatorInput.NO_OPERATOR_ASSIGNED + actual.data.get().node == sptd.participantNode + actual.data.get().em == Optional.of(sptd.emInput) } - def "A SystemParticipantSource should build hp input entity from valid and invalid input data as expected"() { + def "An SystemParticipantSource can enrich SystemParticipantEntityData with SystemParticipantTypeInput correctly"() { given: - def sysPartTypedEntityData = Stream.of(new Try.Success<>(new SystemParticipantTypedEntityData<>(fieldsToAttributes, HpInput, SystemParticipantTestData.hpInput.operator, SystemParticipantTestData.hpInput.node, SystemParticipantTestData.emInput, SystemParticipantTestData.hpTypeInput))) + def entityData = new SystemParticipantEntityData(["type": sptd.evTypeInput.uuid.toString()], EvInput, sptd.evInput.node, sptd.emInput) + def types = map([sptd.evTypeInput]) when: - def hpInputEntityDataOpt = SystemParticipantSource.hpEntityStream(sysPartTypedEntityData, map(thermalBuses)) + def actual = SystemParticipantSource.enrich(types).apply(new Try.Success<>(entityData)) then: - def element = hpInputEntityDataOpt.findFirst().get() - element.success == resultIsPresent - element.data.ifPresent({ - hpInputEntityData -> - assert (hpInputEntityData == resultData) - }) - - where: - thermalBuses | fieldsToAttributes || resultIsPresent || resultData - [] | ["thermalBus": "0d95d7f2-49fb-4d49-8636-383a5220384e"] || false || null - [SystemParticipantTestData.hpInput.thermalBus] | ["bla": "foo"] || false || null - [SystemParticipantTestData.hpInput.thermalBus] | [:] || false || null - [SystemParticipantTestData.hpInput.thermalBus] | ["thermalBus": "0d95d7f2-49fb-4d49-8636-383a5220384f"] || false || null - [SystemParticipantTestData.hpInput.thermalBus] | ["thermalBus": "0d95d7f2-49fb-4d49-8636-383a5220384e"] || true || new HpInputEntityData([:], SystemParticipantTestData.hpInput.operator, SystemParticipantTestData.hpInput.node, SystemParticipantTestData.emInput, SystemParticipantTestData.hpTypeInput, SystemParticipantTestData.hpInput.thermalBus) - } - - def "A SystemParticipantSource should build chp input entity from valid and invalid input data as expected"(List thermalStorages, List thermalBuses, Map fieldsToAttributes, boolean resultIsPresent, ChpInputEntityData resultData) { - given: - def sysPartTypedEntityData = Stream.of(new Try.Success<>(new SystemParticipantTypedEntityData<>(fieldsToAttributes, ChpInput, SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, SystemParticipantTestData.emInput, SystemParticipantTestData.chpTypeInput))) - - when: - def hpInputEntityDataOpt = SystemParticipantSource.chpEntityStream(sysPartTypedEntityData, map(thermalStorages), map(thermalBuses)) - - then: - def element = hpInputEntityDataOpt.findFirst().get() - element.success == resultIsPresent - element.data.ifPresent({ - hpInputEntityData -> - assert (hpInputEntityData == resultData) - }) - - where: - thermalStorages | thermalBuses | fieldsToAttributes || resultIsPresent | resultData - [] | [] | ["thermalBus": "0d95d7f2-49fb-4d49-8636-383a5220384e", "thermalStorage": "8851813b-3a7d-4fee-874b-4df9d724e4b3"] || false | null - [SystemParticipantTestData.chpInput.thermalStorage] | [SystemParticipantTestData.chpInput.thermalBus] | ["bla": "foo"] || false | null - [SystemParticipantTestData.chpInput.thermalStorage] | [SystemParticipantTestData.chpInput.thermalBus] | [:] || false | null - [SystemParticipantTestData.chpInput.thermalStorage] | [SystemParticipantTestData.chpInput.thermalBus] | ["thermalBus": "0d95d7f2-49fb-4d49-8636-383a5220384e", "thermalStorage": "8851813b-3a7d-4fee-874b-4df9d724e4b3"] || true | new ChpInputEntityData([:], SystemParticipantTestData.chpInput.operator, SystemParticipantTestData.chpInput.node, SystemParticipantTestData.emInput, SystemParticipantTestData.chpTypeInput, SystemParticipantTestData.chpInput.thermalBus, SystemParticipantTestData.chpInput.thermalStorage) + actual.success + actual.data.get().typeInput == sptd.evTypeInput } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/ThermalSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/ThermalSourceTest.groovy index 224773490..99f771ff9 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/ThermalSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/ThermalSourceTest.groovy @@ -5,58 +5,33 @@ */ package edu.ie3.datamodel.io.source + import static edu.ie3.test.helper.EntityMap.map -import edu.ie3.datamodel.io.factory.input.AssetInputEntityData -import edu.ie3.datamodel.io.factory.input.ThermalUnitInputEntityData +import edu.ie3.datamodel.io.factory.EntityData import edu.ie3.datamodel.models.input.OperatorInput import edu.ie3.datamodel.models.input.thermal.ThermalBusInput -import edu.ie3.datamodel.models.input.thermal.ThermalUnitInput +import edu.ie3.datamodel.models.input.thermal.ThermalHouseInput import edu.ie3.datamodel.utils.Try import spock.lang.Specification -import java.util.stream.Collectors -import java.util.stream.Stream - class ThermalSourceTest extends Specification { - def "A ThermalSource should build thermal unit input entity from valid and invalid input data as expected"() { + def "A ThermalSource thermalUnitEnricher should work as expected"() { given: - def operator = new OperatorInput(UUID.fromString("8f9682df-0744-4b58-a122-f0dc730f6510"), "testOperator") - def validFieldsToAttributes = [ - "uuid" : "717af017-cc69-406f-b452-e022d7fb516a", - "id" : "test_thermal_unit", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24 15:11:31", - "operatesUntil" : "2020-03-25 15:11:31", - "thermalBus" : "0d95d7f2-49fb-4d49-8636-383a5220384e" - ] - def assetInputEntityData = Stream.of(new Try.Success(new AssetInputEntityData(validFieldsToAttributes, ThermalUnitInput, operator))) + def bus = new ThermalBusInput(UUID.fromString("0d95d7f2-49fb-4d49-8636-383a5220384e"), "test_thermal_bus") + def entityData = new EntityData(["operators": "", "thermalbus": "0d95d7f2-49fb-4d49-8636-383a5220384e"], ThermalHouseInput) + def operators = map([OperatorInput.NO_OPERATOR_ASSIGNED]) + def buses = map([bus]) when: - def resultingDataOpt = ThermalSource.thermalUnitInputEntityDataStream(assetInputEntityData, map(thermalBuses)).collect(Collectors.toList()) + def actual = ThermalSource.thermalUnitEnricher.apply(new Try.Success<>(entityData), operators, buses) then: - resultingDataOpt.size() == 1 - resultingDataOpt.first().data.present == resultIsPresent - resultingDataOpt.first().data.ifPresent({ resultingData -> - assert (resultingData == expectedThermalUnitInputEntityData) - }) - - where: - thermalBuses || resultIsPresent | expectedThermalUnitInputEntityData - [] || false | null // thermal buses are not present -> method should return an empty optional -> do not check for thermal unit entity data - [ - new ThermalBusInput(UUID.fromString("0d95d7f2-49fb-4d49-8636-383a5220384e"), "test_thermal_bus") - ] || true | - new ThermalUnitInputEntityData([ - "uuid" : "717af017-cc69-406f-b452-e022d7fb516a", - "id" : "test_thermal_unit", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24 15:11:31", - "operatesUntil": "2020-03-25 15:11:31"], - ThermalUnitInput, - new OperatorInput(UUID.fromString("8f9682df-0744-4b58-a122-f0dc730f6510"), "testOperator"), - new ThermalBusInput(UUID.fromString("0d95d7f2-49fb-4d49-8636-383a5220384e"), "test_thermal_bus")) + actual.success + actual.data.get().with { + assert it.operatorInput == OperatorInput.NO_OPERATOR_ASSIGNED + assert it.busInput == bus + } } } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy index 61c7bc74e..9a7b15fc5 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvGraphicSourceTest.groovy @@ -70,7 +70,7 @@ class CsvGraphicSourceTest extends Specification implements CsvTestDataMeta { Exception ex = graphicElements.exception.get() ex.class == GraphicSourceException - ex.message.startsWith("1error(s) occurred while initializing graphic elements. edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"LineGraphicInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Linked line with UUID 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not found for entity") + ex.message.startsWith("1 error(s) occurred while initializing graphic elements. edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"LineGraphicInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 91ec3bcf-1777-4d38-af67-0bf7c9fa73c7 was not provided.") } diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy index ee1750e35..b3ad25998 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvRawGridSourceTest.groovy @@ -5,20 +5,11 @@ */ package edu.ie3.datamodel.io.source.csv -import static edu.ie3.test.helper.EntityMap.map - import edu.ie3.datamodel.exceptions.SourceException -import edu.ie3.datamodel.io.factory.input.AssetInputEntityData -import edu.ie3.datamodel.io.factory.input.ConnectorInputEntityData -import edu.ie3.datamodel.io.factory.input.Transformer3WInputEntityData -import edu.ie3.datamodel.io.factory.input.TypedConnectorInputEntityData import edu.ie3.datamodel.io.source.RawGridSource import edu.ie3.datamodel.io.source.TypeSource import edu.ie3.datamodel.models.input.NodeInput import edu.ie3.datamodel.models.input.OperatorInput -import edu.ie3.datamodel.models.input.connector.LineInput -import edu.ie3.datamodel.models.input.connector.SwitchInput -import edu.ie3.datamodel.models.input.connector.Transformer3WInput import edu.ie3.datamodel.models.input.container.RawGridElements import edu.ie3.datamodel.utils.Try import edu.ie3.test.common.GridTestData @@ -26,9 +17,6 @@ import edu.ie3.test.common.GridTestData as rgtd import spock.lang.Shared import spock.lang.Specification -import java.util.stream.Collectors -import java.util.stream.Stream - class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { @Shared RawGridSource source @@ -38,303 +26,6 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { source = new RawGridSource(typeSource, new CsvDataSource(csvSep, gridDefaultFolderPath, fileNamingStrategy)) } - def "The CsvRawGridSource is able to convert single valid AssetInputEntityData to ConnectorInputEntityData"() { - given: "valid input data" - def fieldsToAttributes = [ - "uuid" : "5dc88077-aeb6-4711-9142-db57287640b1", - "id" : "test_switch_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "nodeA" : "4ca90220-74c2-4369-9afa-a18bf068840d", - "nodeB" : "47d29df0-ba2d-4d23-8e75-c82229c5c758", - "closed" : "true" - ] - - def expectedFieldsToAttributes = [ - "uuid" : "5dc88077-aeb6-4711-9142-db57287640b1", - "id" : "test_switch_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "closed" : "true" - ] - - def validAssetEntityInputData = Stream.of(Try.Success.of(new AssetInputEntityData(fieldsToAttributes, SwitchInput))) - - def nodes = map([rgtd.nodeA, rgtd.nodeB]) - - when: "the source tries to convert it" - def connectorDataOption = source.untypedConnectorEntityDataStream(validAssetEntityInputData, nodes) - - then: "everything is fine" - connectorDataOption.forEach { actualTry -> - assert actualTry.isSuccess() - actualTry.data.get().with { - assert fieldsToValues == expectedFieldsToAttributes - assert targetClass == SwitchInput - assert nodeA == rgtd.nodeA - assert nodeB == rgtd.nodeB - } - } - } - - def "The CsvRawGridSource is NOT able to convert single invalid AssetInputEntityData to ConnectorInputEntityData"() { - given: "invalid input data" - def fieldsToAttributes = [ - "uuid" : "5dc88077-aeb6-4711-9142-db57287640b1", - "id" : "test_switch_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "nodeA" : "4ca90220-74c2-4369-9afa-a18bf068840d", - "nodeB" : "620d35fc-34f8-48af-8020-3897fe75add7", - "closed" : "true" - ] - - def validAssetEntityInputData = Stream.of(Try.Success.of(new AssetInputEntityData(fieldsToAttributes, SwitchInput))) - - def nodes = map([rgtd.nodeA, rgtd.nodeB]) - - when: "the source tries to convert it" - def connectorDataOption = source.untypedConnectorEntityDataStream(validAssetEntityInputData, nodes) - - then: "it returns a Failure" - connectorDataOption.allMatch(Try::isFailure) - } - - - def "The CsvRawGridSource is able to convert a stream of valid AssetInputEntityData to ConnectorInputEntityData"() { - given: "valid input data" - def validStream = Stream.of( - Try.Success.of(new AssetInputEntityData([ - "uuid" : "5dc88077-aeb6-4711-9142-db57287640b1", - "id" : "test_switch_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "nodeA" : "4ca90220-74c2-4369-9afa-a18bf068840d", - "nodeB" : "47d29df0-ba2d-4d23-8e75-c82229c5c758", - "closed" : "true" - ], SwitchInput)), - Try.Success.of(new AssetInputEntityData([ - "uuid" : "91ec3bcf-1777-4d38-af67-0bf7c9fa73c7", - "id" : "test_lineCtoD", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "nodeA" : "bd837a25-58f3-44ac-aa90-c6b6e3cd91b2", - "nodeB" : "6e0980e0-10f2-4e18-862b-eb2b7c90509b", - "parallelDevices" : "2", - "type" : "3bed3eb3-9790-4874-89b5-a5434d408088", - "length" : "0.003", - "geoPosition" : "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", - "olmCharacteristic" : "olm:{(0.0,1.0)}" - ], - LineInput) - )) - - def expectedSet = [ - new ConnectorInputEntityData([ - "uuid" : "5dc88077-aeb6-4711-9142-db57287640b1", - "id" : "test_switch_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "closed" : "true" - ], - SwitchInput, - rgtd.nodeA, - rgtd.nodeB - ), - new ConnectorInputEntityData([ - "uuid" : "91ec3bcf-1777-4d38-af67-0bf7c9fa73c7", - "id" : "test_lineCtoD", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "parallelDevices" : "2", - "type" : "3bed3eb3-9790-4874-89b5-a5434d408088", - "length" : "0.003", - "geoPosition" : "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", - "olmCharacteristic" : "olm:{(0.0,1.0)}" - ], - LineInput, - rgtd.nodeC, - rgtd.nodeD - ) - ] as Set - - def nodes = map([ - rgtd.nodeA, - rgtd.nodeB, - rgtd.nodeC, - rgtd.nodeD - ]) - - when: "the source tries to convert it" - def actualSet = source.untypedConnectorEntityDataStream(validStream, nodes).collect(Collectors.toSet()) - - then: "everything is fine" - actualSet.size() == expectedSet.size() - def result = Try.scanCollection(actualSet, List) - - result.success - result.data.get().toList().containsAll(expectedSet) - } - - def "The CsvRawGridSource is able to convert a stream of valid ConnectorInputEntityData to TypedConnectorInputEntityData"() { - given: "valid input data" - def validStream = Stream.of(Try.Success.of( - new ConnectorInputEntityData([ - "uuid" : "91ec3bcf-1777-4d38-af67-0bf7c9fa73c7", - "id" : "test_lineCtoD", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "parallelDevices" : "2", - "type" : "3bed3eb3-9790-4874-89b5-a5434d408088", - "length" : "0.003", - "geoPosition" : "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", - "olmCharacteristic" : "olm:{(0.0,1.0)}" - ], - LineInput, - rgtd.nodeC, - rgtd.nodeD - )), - Try.Success.of(new ConnectorInputEntityData([ - "uuid" : "92ec3bcf-1777-4d38-af67-0bf7c9fa73c7", - "id" : "test_line_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "parallelDevices" : "2", - "type" : "3bed3eb3-9790-4874-89b5-a5434d408088", - "length" : "0.003", - "geoPosition" : "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", - "olmCharacteristic" : "olm:{(0.0,1.0)}" - ], LineInput, - rgtd.nodeA, - rgtd.nodeB - ))) as Stream> - - def expectedSet = [ - new TypedConnectorInputEntityData<>([ - "uuid" : "91ec3bcf-1777-4d38-af67-0bf7c9fa73c7", - "id" : "test_lineCtoD", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "parallelDevices" : "2", - "length" : "0.003", - "geoPosition" : "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", - "olmCharacteristic" : "olm:{(0.0,1.0)}" - ], - LineInput, - rgtd.nodeC, - rgtd.nodeD, - rgtd.lineTypeInputCtoD - ), - new TypedConnectorInputEntityData<>([ - "uuid" : "92ec3bcf-1777-4d38-af67-0bf7c9fa73c7", - "id" : "test_line_AtoB", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "parallelDevices" : "2", - "length" : "0.003", - "geoPosition" : "{ \"type\": \"LineString\", \"coordinates\": [[7.411111, 51.492528], [7.414116, 51.484136]]}", - "olmCharacteristic" : "olm:{(0.0,1.0)}" - ], LineInput, - rgtd.nodeA, - rgtd.nodeB, - rgtd.lineTypeInputCtoD - ) - ] - - def availableTypes = map([rgtd.lineTypeInputCtoD]) - - when: "the source tries to convert it" - def actualSet = source.typedConnectorEntityDataStream(validStream, availableTypes).collect(Collectors.toSet()) - - then: "everything is fine" - actualSet.size() == expectedSet.size() - def result = Try.scanCollection(actualSet, List) - - result.success - result.data.get().toList().containsAll(expectedSet) - } - - def "The CsvRawGridSource is able to add the third node for a three winding transformer to a stream of candidates"() { - given: "suitable input data" - def inputStream = Stream.of(Try.of(() -> new TypedConnectorInputEntityData([ - "uuid" : "cc327469-7d56-472b-a0df-edbb64f90e8f", - "id" : "3w_test", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "nodeC" : "bd837a25-58f3-44ac-aa90-c6b6e3cd91b2", - "parallelDevices" : "1", - "tapPos" : "0", - "autoTap" : "true" - ], - Transformer3WInput, - rgtd.nodeA, - rgtd.nodeB, - rgtd.transformerTypeAtoBtoC), SourceException), - Try.of(() -> new TypedConnectorInputEntityData([ - "uuid" : "cc327469-7d56-472b-a0df-edbb64f90e8f", - "id" : "3w_test", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "nodeC" : "bd8927b4-0ca9-4dd3-b645-468e6e433160", - "parallelDevices" : "1", - "tapPos" : "0", - "autoTap" : "true" - ], - Transformer3WInput, - rgtd.nodeA, - rgtd.nodeB, - rgtd.transformerTypeAtoBtoC), SourceException)) - - def availableNodes = map([ - rgtd.nodeA, - rgtd.nodeB, - rgtd.nodeC - ]) - - def expected = new Transformer3WInputEntityData([ - "uuid" : "cc327469-7d56-472b-a0df-edbb64f90e8f", - "id" : "3w_test", - "operator" : "8f9682df-0744-4b58-a122-f0dc730f6510", - "operatesFrom" : "2020-03-24T15:11:31Z", - "operatesUntil" : "2020-03-24T15:11:31Z", - "parallelDevices" : "1", - "tapPos" : "0", - "autoTap" : "true" - ], - Transformer3WInput, - rgtd.nodeA, - rgtd.nodeB, - rgtd.nodeC, - rgtd.transformerTypeAtoBtoC) - - when: "the sources tries to add nodes" - def actualSet = source.transformer3WEntityDataStream(inputStream, availableNodes).collect(Collectors.toSet()) - def successes = actualSet.stream().filter { - it.success - }.toList() - def failures = actualSet.stream().filter { - it.failure - }.toList() - - then: "everything is fine" - actualSet.size() == 2 - successes.get(0).data.get() == expected - failures.get(0).exception.get().class == SourceException - } - def "The CsvRawGridSource is able to load all nodes from file"() { when: "loading all nodes from file" def actualSet = source.getNodes() @@ -518,34 +209,34 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { when: "loading a total grid structure from file" def actual = source.getGridData() def expected = new RawGridElements( - [ - rgtd.nodeA, - rgtd.nodeB, - rgtd.nodeC, - rgtd.nodeD, - rgtd.nodeE, - rgtd.nodeF, - rgtd.nodeG - ] as Set, - [ - rgtd.lineAtoB, - rgtd.lineCtoD - ] as Set, - [ - GridTestData.transformerBtoD, - GridTestData.transformerBtoE, - GridTestData.transformerCtoE, - GridTestData.transformerCtoF, - GridTestData.transformerCtoG - ] as Set, - [ - GridTestData.transformerAtoBtoC - ] as Set, - [rgtd.switchAtoB] as Set, - [ - rgtd.measurementUnitInput - ] as Set - ) + [ + rgtd.nodeA, + rgtd.nodeB, + rgtd.nodeC, + rgtd.nodeD, + rgtd.nodeE, + rgtd.nodeF, + rgtd.nodeG + ] as Set, + [ + rgtd.lineAtoB, + rgtd.lineCtoD + ] as Set, + [ + GridTestData.transformerBtoD, + GridTestData.transformerBtoE, + GridTestData.transformerCtoE, + GridTestData.transformerCtoF, + GridTestData.transformerCtoG + ] as Set, + [ + GridTestData.transformerAtoBtoC + ] as Set, + [rgtd.switchAtoB] as Set, + [ + rgtd.measurementUnitInput + ] as Set + ) then: "all elements are there" actual != null @@ -617,6 +308,6 @@ class CsvRawGridSourceTest extends Specification implements CsvTestDataMeta { Exception ex = rawGridElements.exception.get() ex.class == SourceException - ex.message.startsWith("edu.ie3.datamodel.exceptions.FailureException: 2 exception(s) occurred within \"LineInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Linked nodeA") + ex.message.startsWith("edu.ie3.datamodel.exceptions.FailureException: 2 exception(s) occurred within \"LineInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: Entity with uuid ") } } \ No newline at end of file diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy index 91486c5eb..43e4812d3 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy @@ -10,8 +10,6 @@ import static edu.ie3.test.helper.EntityMap.map import edu.ie3.datamodel.exceptions.SourceException import edu.ie3.datamodel.exceptions.SystemParticipantsException import edu.ie3.datamodel.io.source.* -import edu.ie3.datamodel.models.input.OperatorInput -import edu.ie3.datamodel.models.input.system.* import edu.ie3.datamodel.utils.Try import edu.ie3.test.common.SystemParticipantTestData as sptd import spock.lang.Specification @@ -79,9 +77,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat Exception ex = systemParticipants.exception.get() ex.class == SystemParticipantsException ex.message.startsWith("10 error(s) occurred while initializing system participants. " + - "edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within \"FixedFeedInInput\" data, one is: " + - "edu.ie3.datamodel.exceptions.FactoryException: edu.ie3.datamodel.exceptions.SourceException: " + - "Linked node with UUID 4ca90220-74c2-4369-9afa-a18bf068840d was not found for entity AssetInputEntityData") + "edu.ie3.datamodel.exceptions.FailureException: 1 exception(s) occurred within " + + "\"FixedFeedInInput\" data, one is: edu.ie3.datamodel.exceptions.FactoryException: " + + "edu.ie3.datamodel.exceptions.SourceException: Entity with uuid 4ca90220-74c2-4369-9afa-a18bf068840d was not provided.") } def "A SystemParticipantSource with csv input should return data from valid input file as expected"() { @@ -159,7 +157,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: operators | types | thermalBuses || resultingSize | resultingSet - [] | [sptd.hpInput.type] | [sptd.hpInput.thermalBus] || 1 | [new HpInput(sptd.hpInput.uuid, sptd.hpInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.hpInput.operationTime, sptd.hpInput.node, sptd.hpInput.thermalBus, sptd.hpInput.qCharacteristics, sptd.emInput, sptd.hpInput.type)] [] | [] | [] || 0 | [] [] | [] | [] || 0 | [] [sptd.hpInput.operator] | [] | [] || 0 | [] @@ -185,7 +182,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: operators | types | thermalBuses | thermalStorages || resultingSet - [] | [sptd.chpInput.type] | [sptd.chpInput.thermalBus] | [sptd.chpInput.thermalStorage] || [new ChpInput(sptd.chpInput.uuid, sptd.chpInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.chpInput.operationTime, sptd.chpInput.node, sptd.chpInput.thermalBus, sptd.chpInput.qCharacteristics, sptd.emInput, sptd.chpInput.type, sptd.chpInput.thermalStorage, sptd.chpInput.marketReaction)] [] | [] | [] | [] as List || [] [] | [] | [] | [] as List || [] [sptd.chpInput.operator] | [] | [] | [] as List || [] @@ -211,7 +207,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: operators | types || resultingSet - [] | [sptd.evInput.type] || [new EvInput(sptd.evInput.uuid, sptd.evInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.evInput.operationTime, sptd.evInput.node, sptd.evInput.qCharacteristics, sptd.emInput, sptd.evInput.type)] [sptd.evInput.operator] | [] || [] [] | [] || [] } @@ -235,7 +230,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: operators | types || resultingSet - [] | [sptd.wecInput.type] || [new WecInput(sptd.wecInput.uuid, sptd.wecInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.wecInput.operationTime, sptd.wecInput.node, sptd.wecInput.qCharacteristics, sptd.emInput, sptd.wecInput.type, sptd.wecInput.marketReaction)] [sptd.wecInput.operator] | [] || [] [] | [] || [] } @@ -259,7 +253,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: operators | types || resultingSet - [] | [sptd.storageInput.type] || [new StorageInput(sptd.storageInput.uuid, sptd.storageInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.storageInput.operationTime, sptd.storageInput.node, sptd.storageInput.qCharacteristics, sptd.emInput, sptd.storageInput.type)] [sptd.storageInput.operator] | [] || [] [] | [] || [] } @@ -283,7 +276,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: operators | types || resultingSet - [] | [sptd.bmInput.type] || [new BmInput(sptd.bmInput.uuid, sptd.bmInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.bmInput.operationTime, sptd.bmInput.node, sptd.bmInput.qCharacteristics, sptd.emInput, sptd.bmInput.type, sptd.bmInput.marketReaction, sptd.bmInput.costControlled, sptd.bmInput.feedInTariff)] [sptd.bmInput.operator] | [] || [] [] | [] || [] [] | [] || [] @@ -307,7 +299,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: nodes | operators || resultingSet - [sptd.evcsInput.node] | [] || [new EvcsInput(sptd.evcsInput.uuid, sptd.evcsInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.evcsInput.operationTime, sptd.evcsInput.node, sptd.evcsInput.qCharacteristics, sptd.emInput, sptd.evcsInput.type, sptd.evcsInput.chargingPoints, sptd.evcsInput.cosPhiRated, sptd.evcsInput.locationType, sptd.evcsInput.v2gSupport)] [] | [sptd.evcsInput.operator] || [] [] | [] || [] } @@ -330,7 +321,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: nodes | operators || resultingSet - [sptd.loadInput.node] | [] || [new LoadInput(sptd.loadInput.uuid, sptd.loadInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.loadInput.operationTime, sptd.loadInput.node, sptd.loadInput.qCharacteristics, sptd.emInput, sptd.loadInput.loadProfile, sptd.loadInput.dsm, sptd.loadInput.eConsAnnual, sptd.loadInput.sRated, sptd.loadInput.cosPhiRated)] [] | [sptd.loadInput.operator] || [] [] | [] || [] } @@ -353,7 +343,6 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat where: nodes | operators || resultingSet - [sptd.pvInput.node] | [] || [new PvInput(sptd.pvInput.uuid, sptd.pvInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.pvInput.operationTime, sptd.pvInput.node, sptd.pvInput.qCharacteristics, sptd.emInput, sptd.pvInput.albedo, sptd.pvInput.azimuth, sptd.pvInput.etaConv, sptd.pvInput.elevationAngle, sptd.pvInput.kG, sptd.pvInput.kT, sptd.pvInput.marketReaction, sptd.pvInput.sRated, sptd.pvInput.cosPhiRated)] [] | [sptd.pvInput.operator] || [] [] | [] || [] } @@ -375,9 +364,8 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - nodes | operators || resultingSet - [sptd.fixedFeedInInput.node] | [] as List || [new FixedFeedInInput(sptd.fixedFeedInInput.uuid, sptd.fixedFeedInInput.id, OperatorInput.NO_OPERATOR_ASSIGNED, sptd.fixedFeedInInput.operationTime, sptd.fixedFeedInInput.node, sptd.fixedFeedInInput.qCharacteristics, sptd.emInput, sptd.fixedFeedInInput.sRated, sptd.fixedFeedInInput.cosPhiRated)] - [] | [sptd.fixedFeedInInput.operator] as List || [] - [] | [] as List || [] + nodes | operators || resultingSet + [] | [sptd.fixedFeedInInput.operator] || [] + [] | [] || [] } } From 36fc1e522f4e802bd8bb62da753193c8a1a4f41a Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Tue, 12 Mar 2024 17:39:40 +0100 Subject: [PATCH 04/22] Some improvements --- .../edu/ie3/datamodel/io/source/EntitySource.java | 11 ++++++----- .../ie3/datamodel/io/source/EntitySourceTest.groovy | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index 0f4054202..5d61a1f05 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -196,7 +196,7 @@ TryFunction enrichWithDefault( .zip( extract(entityData, fieldName, entities) .orElse(() -> Try.Success.of(defaultEntity))) - .map(builder(List.of(fieldName), fcn)); + .map(enrich(List.of(fieldName), fcn)); } /** @@ -215,7 +215,7 @@ protected static TryFunction entityData .zip(extract(entityData, fieldName, entities)) - .map(builder(List.of(fieldName), fcn)); + .map(enrich(List.of(fieldName), fcn)); } /** @@ -246,12 +246,11 @@ TryFunction biEnrich( extract(entityData, fieldName1, entities1) .zip(extract(entityData, fieldName2, entities2))) .map( - builder( + enrich( List.of(fieldName1, fieldName2), (data, pair) -> fcn.apply(data, pair.getKey(), pair.getValue()))); } - // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= /** * Method to build an {@link EntityData}. * @@ -262,7 +261,7 @@ TryFunction biEnrich( * @param type of entities * @param type of returned entity data */ - private static Function, R> builder( + protected static Function, R> enrich( List fieldNames, BiFunction fcn) { return pair -> { E data = pair.getKey(); @@ -277,6 +276,8 @@ private static Function Date: Mon, 15 Apr 2024 11:14:25 +0200 Subject: [PATCH 05/22] Adapting `CHANGELOG`. --- CHANGELOG.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3a9b8a1f6..9ce290e62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,7 +7,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased/Snapshot] ### Added - +- Enhancing `VoltageLevel` with `equals` method [#1063](https://github.com/ie3-institute/PowerSystemDataModel/issues/1063) ### Fixed @@ -34,7 +34,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Added test for invalid input data in `CsvRawGridSource` [#1021](https://github.com/ie3-institute/PowerSystemDataModel/issues/1021) - Added `CsvThermalGridSource` [#1009](https://github.com/ie3-institute/PowerSystemDataModel/issues/1009) - Enhance documentation for CSV timeseries [#825](https://github.com/ie3-institute/PowerSystemDataModel/issues/825) -- Enhancing `VoltageLevel` with `equals` method [#1063](https://github.com/ie3-institute/PowerSystemDataModel/issues/1063) ### Fixed - Fixed Couchbase integration tests that randomly failed [#755](https://github.com/ie3-institute/PowerSystemDataModel/issues/755) From 58a295d1bb6e54c38336689dbf3253f4799613fc Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Thu, 2 May 2024 11:45:05 +0200 Subject: [PATCH 06/22] Some minor adaption. --- .../java/edu/ie3/datamodel/io/source/IdCoordinateSource.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java index 0a2ebe516..33fb8b9f5 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/IdCoordinateSource.java @@ -112,7 +112,8 @@ public List calculateCoordinateDistances( * @return either a list with one exact match or a list of corner points (default implementation: * max. 4 points) */ - List findCornerPoints(Point coordinate, ComparableQuantity distance); + public abstract List findCornerPoints( + Point coordinate, ComparableQuantity distance); /** * Method for finding the corner points of a given coordinate. If a point matches the given From b5372768a6ac665be54c1d6c9b870555e8a06a90 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Thu, 13 Jun 2024 12:35:43 +0200 Subject: [PATCH 07/22] Adapting to changes in #1031 --- .../java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java index 789b0757e..6bd6bfff1 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java @@ -79,7 +79,6 @@ public CsvWeatherSource( @Override public Optional> getSourceFields() { return dataSource - .connector .getCsvIndividualTimeSeriesMetaInformation(ColumnScheme.WEATHER) .values() .stream() From bc2deea642ec1fabe5b4b321de68123cf1c32267 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 14 Jun 2024 11:10:51 +0200 Subject: [PATCH 08/22] Fixing entries in `CHANGELOG`. --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 609e6327c..6fc54cd70 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed `MappingEntryies` not getting processed by adding `Getter` methods for record fields [#1084](https://github.com/ie3-institute/PowerSystemDataModel/issues/1084) +- Fixed "depth of discharge" in documentation [#872](https://github.com/ie3-institute/PowerSystemDataModel/issues/872) ### Changed - Improvements to the search for corner points in `IdCoordinateSource` [#1016](https://github.com/ie3-institute/PowerSystemDataModel/issues/1016) @@ -24,7 +25,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed `equals` of `ResultEntity` and `TimeSeriesEntry` [#1037](https://github.com/ie3-institute/PowerSystemDataModel/issues/1037) -- Fixed "depth of discharge" in documentation [#872](https://github.com/ie3-institute/PowerSystemDataModel/issues/872) ## [5.0.0] - 2024-03-06 From e8945d98d8a0d1184c5ff4764189a0bddefe02c7 Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Fri, 14 Jun 2024 13:53:05 +0200 Subject: [PATCH 09/22] Implementing requested changes. --- docs/uml/main/EntitySourceClassDiagram.puml | 159 +++++++++++++++--- .../io/source/AssetEntitySource.java | 9 +- .../io/source/EnergyManagementSource.java | 2 +- .../ie3/datamodel/io/source/EntitySource.java | 90 +++++----- .../datamodel/io/source/GraphicSource.java | 2 +- .../datamodel/io/source/RawGridSource.java | 2 +- .../io/source/ResultEntitySource.java | 4 +- .../io/source/SystemParticipantSource.java | 30 ++-- .../datamodel/io/source/WeatherSource.java | 2 +- .../couchbase/CouchbaseWeatherSource.java | 10 +- .../io/source/csv/CsvIdCoordinateSource.java | 5 - .../io/source/csv/CsvTimeSeriesSource.java | 11 +- .../io/source/csv/CsvWeatherSource.java | 5 - .../edu/ie3/datamodel/utils/QuadFunction.java | 2 +- .../java/edu/ie3/datamodel/utils/Try.java | 11 ++ .../io/source/AssetEntitySourceTest.groovy | 2 +- .../io/source/EntitySourceTest.groovy | 8 +- .../source/SystemParticipantSourceTest.groovy | 2 +- 18 files changed, 245 insertions(+), 111 deletions(-) diff --git a/docs/uml/main/EntitySourceClassDiagram.puml b/docs/uml/main/EntitySourceClassDiagram.puml index 56ac69c40..a9ae77130 100644 --- a/docs/uml/main/EntitySourceClassDiagram.puml +++ b/docs/uml/main/EntitySourceClassDiagram.puml @@ -3,27 +3,38 @@ note "Assuming all classes to implement \nthe abstract methods of their interfaces\n\n" as generalNotes abstract class EntitySource { - # DataSource dataSource - # {static} Try enrichEntityData(E, String, Map, BiFunction) - # {static} Try enrichEntityData(E, String, Map, String, Map, TriFunction) - # {static} Try getLinkedEntity(EntityData, String, Map) - # {static} Try optionallyEnrichEntityData(E, String, Map, T, BiFunction) - # Stream> buildNodeAssetEntities(Class, Map, Map) - # {static} Stream> nodeAssetInputEntityDataStream(Stream>, Map) - # Stream> buildAssetInputEntities(Class, Map) - # {static} Stream> assetInputEntityDataStream(Stream>, Map) - # Stream> buildEntityData(Class) - # {static} Map unpackMap(Stream>, Class) throws SourceException - # {static} Set unpackSet(Stream>, Class) throws SourceException - # {static} Stream unpackSet(Stream>, Class) throws SourceException + + void validate() throws ValidationException } +EntitySource <|-- EnergyManagementSource EntitySource <|-- GraphicSource +EntitySource <|-- IdCoordinateSource +IdCoordinateSource <|-- CsvIdCoordinateSource +IdCoordinateSource <|-- SqlIdCoordinateSource EntitySource <|-- RawGridSource EntitySource <|-- ResultEntitySource EntitySource <|-- SystemParticipantSource EntitySource <|-- ThermalSource +EntitySource <|-- TimeSeriesMappingSource +TimeSeriesMappingSource <|-- CsvTimeSeriesMappingSource +TimeSeriesMappingSource <|-- SqlTimeSeriesMappingSource +EntitySource <|-- TimeSeriesSource +TimeSeriesSource <|-- CsvTimeSeriesSource +TimeSeriesSource <|-- SqlTimeSeriesSource EntitySource <|-- TypeSource +EntitySource <|-- WeatherSource +WeatherSource <|-- CouchbaseWeatherSource +WeatherSource <|-- CsvWeatherSource +WeatherSource <|-- InfluxDbWeatherSource +WeatherSource <|-- SqlWeatherSource + +class EnergyManagementSource { + - TypeSource typeSource + - EmInputFactory emInputFactory + + EnergyManagementSource(TypeSource, DataSource) + + Map getEmUnits() throws SourceException + + Map getEmUnits(Map) throws SourceException +} class GraphicSource { - TypeSource typeSource @@ -39,6 +50,34 @@ class GraphicSource { + Set getLineGraphicInput(Map) throws SourceException } +abstract class IdCoordinateSource { + + Optional> getSourceFields() throws SourceException + + Optional getCoordinate(int) + + Collection getCoordinates(int[]) + + Optional getId(Point) + + Collection getAllCoordinates() + + List getNearestCoordinates(Point, int) + + List getClosestCoordinates(Point, int, ComparableQuantity) + + List calculateCoordinateDistances(Point, int, Collection) + + List findCornerPoints(Point, ComparableQuantity) + + List findCornerPoints(Point, Collection) +} + +class CsvIdCoordinateSource { + - Map idToCoordinate; + - Map coordinateToId; + - CsvDataSource dataSource; + - IdCoordinateFactory factory; + + CsvIdCoordinateSource(IdCoordinateFactory, CsvDataSource) throws SourceException + + int getCoordinateCount() +} + +class SqlIdCoordinateSource { + - SqlDataSource dataSource + - SqlIdCoordinateFactory factory + + SqlIdCoordinateSource(SqlIdCoordinateFactory, String, SqlDataSource) +} + class RawGridSource { - TypeSource typeSource - NodeInputFactory nodeInputFactory @@ -70,9 +109,10 @@ class ResultEntitySource { - SwitchResultFactory switchResultFactory - NodeResultFactory nodeResultFactory - ConnectorResultFactory connectorResultFactory + - CongestionResultFactory congestionResultFactory - FlexOptionsResultFactory flexOptionsResultFactory + ResultEntitySource(DataSource) - + ResultEntitySource(DataSource, String) + + ResultEntitySource(DataSource, DateTimeFormatter) + Set getNodeResults() throws SourceException + Set getSwitchResults() throws SourceException + Set getLineResults() throws SourceException @@ -92,12 +132,14 @@ class ResultEntitySource { + Set getCylindricalStorageResult() throws SourceException + Set getThermalHouseResults() throws SourceException + Set getEmResults() throws SourceException + + Set getCongestionResults() throws SourceException } class SystemParticipantSource{ - TypeSource typeSource - RawGridSource rawGridSource - ThermalSource thermalSource + - EnergyManagementSource energyManagementSource - BmInputFactory bmInputFactory - ChpInputFactory chpInputFactory - EvInputFactory evInputFactory @@ -108,7 +150,7 @@ class SystemParticipantSource{ - StorageInputFactory storageInputFactory - WecInputFactory wecInputFactory - EvcsInputFactory evcsInputFactory - + SystemParticipantSource(TypeSource, ThermalSource, RawGridSource, DataSource) + + SystemParticipantSource(TypeSource, ThermalSource, RawGridSource, EnergyManagementSource, DataSource) + SystemParticipants getSystemParticipants() throws SourceException + SystemParticipants getSystemParticipants(Map, Map) throws SourceException + Set getBmPlants() throws SourceException @@ -141,20 +183,65 @@ class ThermalSource { + ThermalSource(TypeSource, DataSource) + Map getThermalBuses() throws SourceException + Map getThermalBuses(Map) throws SourceException - + Set getThermalStorages() throws SourceException - + Set getThermalStorages(Map, Map) throws SourceException - + Set getThermalHouses() throws SourceException - + Set getThermalHouses(Map, Map) throws SourceException + + Map getThermalStorages() throws SourceException + + Map getThermalStorages(Map, Map) throws SourceException + + Map getThermalHouses() throws SourceException + + Map getThermalHouses(Map, Map) throws SourceException + Set getCylindricStorages() throws SourceException + Set getCylindricStorages(Map, Map) throws SourceException } +abstract class TimeSeriesMappingSource { + - TimeSeriesMappingFactory mappingFactory + + Map getMapping() throws SourceException + + Optional getTimeSeriesUuid(UUID) throws SourceException + + Stream> getMappingSourceData() throws SourceException + + Optional> getSourceFields() throws SourceException +} + +class CsvTimeSeriesMappingSource { + - CsvDataSource dataSource + + CsvTimeSeriesMappingSource(String, Path, FileNamingStrategy) +} + +class SqlTimeSeriesMappingSource { + - EntityPersistenceNamingStrategy entityPersistenceNamingStrategy + - SqlDataSource dataSource + + SqlTimeSeriesMappingSource(SqlConnector, String, EntityPersistenceNamingStrategy) +} + +abstract class TimeSeriesSource { + - Class valueClass + - TimeBasedSimpleValueFactory valueFactory + + TimeSeriesSource(Class, TimeBasedSimpleValueFactory) + + IndividualTimeSeries getTimeSeries() + + IndividualTimeSeries getTimeSeries(ClosedInterval) throws SourceException + + Optional getValue(ZonedDateTime) throws SourceException +} + +class CsvTimeSeriesSource { + - IndividualTimeSeries timeSeries + - CsvDataSource dataSource + - Path filePath + + {static} CsvTimeSeriesSource getSource(String, Path, FileNamingStrategy, CsvIndividualTimeSeriesMetaInformation) + + CsvTimeSeriesSource(String, Path, FileNamingStrategy, UUID, Path, Class, TimeBasedSimpleValueFactory) +} + +class SqlTimeSeriesSource { + - SqlDataSource dataSource + - UUID timeSeriesUuid + + SqlTimeSeriesSource(SqlDataSource, UUID, Class, TimeBasedSimpleValueFactory) + + SqlTimeSeriesSource(SqlConnector, String, DatabaseNamingStrategy, UUID, Class, TimeBasedSimpleValueFactory) + + SqlTimeSeriesSource createSource(SqlConnector, String, DatabaseNamingStrategy, IndividualTimeSeriesMetaInformation, DateTimeFormatter) throws SourceException +} + class TypeSource { - OperatorInputFactory operatorInputFactory - Transformer2WTypeInputFactory transformer2WTypeInputFactory - LineTypeInputFactory lineTypeInputFactory - Transformer3WTypeInputFactory transformer3WTypeInputFactory - SystemParticipantTypeInputFactory systemParticipantTypeInputFactory + - DataSource dataSource + TypeSource(DataSource) + Map getTransformer2WTypes() throws SourceException + Map getTransformer3WTypes() throws SourceException @@ -167,4 +254,38 @@ class TypeSource { + Map getWecTypes() throws SourceException + Map getEvTypes() throws SourceException } + +abstract class WeatherSource { + - TimeBasedWeatherValueFactory weatherFactory + - IdCoordinateSource idCoordinateSource + + WeatherSource(IdCoordinateSource, TimeBasedWeatherValueFactory) + + Optional> getSourceFields() throws SourceException + + Map> getWeather(ClosedInterval) throws SourceException + + Map> getWeather(ClosedInterval, Collection) throws SourceException + + Optional> getWeather(ZonedDateTime, Point) throws SourceException +} + +class CouchbaseWeatherSource { + - CouchbaseConnector connector + + CouchbaseWeatherSource(CouchbaseConnector, IdCoordinateSource, String, TimeBasedWeatherValueFactory, String) + + CouchbaseWeatherSource(CouchbaseConnector, IdCoordinateSource, String, String, TimeBasedWeatherValueFactory, String) +} + +class CsvWeatherSource { + - CsvDataSource dataSource + - Map> coordinateToTimeSeries + + CsvWeatherSource(String, Path, FileNamingStrategy, IdCoordinateSource, TimeBasedWeatherValueFactory) throws SourceException +} + +class InfluxDbWeatherSource { + - InfluxDbConnector connector + + InfluxDbWeatherSource(InfluxDbConnector, IdCoordinateSource, TimeBasedWeatherValueFactory) + + IndividualTimeSeries getWeather(ClosedInterval, Point) +} + +class SqlWeatherSource { + - SqlDataSource dataSource + + SqlWeatherSource(SqlConnector, IdCoordinateSource, String, String, TimeBasedWeatherValueFactory) +} + @enduml \ No newline at end of file diff --git a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java index 14a269358..1757fdacd 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/AssetEntitySource.java @@ -92,7 +92,7 @@ Stream getTypedConnectorEntities( entityClass, dataSource, factory, - data -> connectorEnricher.andThen(enrich(types)).apply(data, operators, nodes)); + data -> connectorEnricher.andThen(enrichConnector(types)).apply(data, operators, nodes)); } /** @@ -103,8 +103,9 @@ Stream getTypedConnectorEntities( * @param type of types */ private static - TryFunction> enrich(Map types) { - BiFunction> fcn = TypedConnectorInputEntityData::new; - return entityData -> enrich(TYPE, types, fcn).apply(entityData); + WrappedFunction> enrichConnector(Map types) { + BiFunction> typeEnricher = + TypedConnectorInputEntityData::new; + return entityData -> enrich(TYPE, types, typeEnricher).apply(entityData); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java index 3396b02e5..8a5cce317 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EnergyManagementSource.java @@ -74,7 +74,7 @@ public Map getEmUnits(Map operators) throws /** * Since each EM can itself be controlled by another EM, it does not suffice to link {@link - * EmInput}s via {@link EntitySource#enrich} as we do for system participants in {@link + * EmInput}s via {@link EntitySource#enrichFunction} as we do for system participants in {@link * SystemParticipantSource}. Instead, we use a recursive approach, starting with EMs at root level * (which are not EM-controlled themselves). * diff --git a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java index 5d61a1f05..95dd1e0dc 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/EntitySource.java @@ -123,7 +123,7 @@ protected static Map getEntities( * @param entityClass class of the entity * @param dataSource source for the entity * @param factory to build the entity - * @param fcn function to enrich the given entity data + * @param enrichFunction function to enrich the given entity data * @return a set of {@link Entity}s * @param type of entity * @param type of entity data @@ -133,9 +133,10 @@ protected static Stream getEntities( Class entityClass, DataSource dataSource, EntityFactory factory, - TryFunction fcn) + WrappedFunction enrichFunction) throws SourceException { - return unpack(buildEntityData(entityClass, dataSource, fcn).map(factory::get), entityClass); + return unpack( + buildEntityData(entityClass, dataSource, enrichFunction).map(factory::get), entityClass); } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -165,13 +166,15 @@ protected static Stream> buildEntityData( * * @param entityClass class of the entity * @param dataSource source for the data - * @param fcn to convert {@link EntityData} to {@link E} + * @param converter to convert {@link EntityData} to {@link E} * @return an entity data * @param type of entity data */ protected static Stream> buildEntityData( - Class entityClass, DataSource dataSource, TryFunction fcn) { - return buildEntityData(entityClass, dataSource).map(fcn); + Class entityClass, + DataSource dataSource, + WrappedFunction converter) { + return buildEntityData(entityClass, dataSource).map(converter); } // -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-= @@ -182,21 +185,24 @@ protected static Stream> buildEnt * @param fieldName name of the field * @param entities map: uuid to {@link Entity} * @param defaultEntity entity that should be used if no other entity was extracted - * @param fcn to build the returned {@link EntityData} - * @return a new entity data + * @param buildingFcn to build the returned {@link EntityData} + * @return an enrich function * @param type of entity data * @param type of entity * @param type of returned entity data */ protected static - TryFunction enrichWithDefault( - String fieldName, Map entities, T defaultEntity, BiFunction fcn) { + WrappedFunction enrichWithDefault( + String fieldName, + Map entities, + T defaultEntity, + BiFunction buildingFcn) { return entityData -> entityData .zip( - extract(entityData, fieldName, entities) + extractFunction(entityData, fieldName, entities) .orElse(() -> Try.Success.of(defaultEntity))) - .map(enrich(List.of(fieldName), fcn)); + .map(enrichFunction(List.of(fieldName), buildingFcn)); } /** @@ -204,18 +210,18 @@ TryFunction enrichWithDefault( * * @param fieldName name of the field * @param entities map: uuid to {@link Entity} - * @param fcn to build the returned {@link EntityData} - * @return a new entity data + * @param buildingFcn to build the returned {@link EntityData} + * @return an enrich function * @param type of entity data * @param type of entity * @param type of returned entity data */ - protected static TryFunction enrich( - String fieldName, Map entities, BiFunction fcn) { + protected static WrappedFunction enrich( + String fieldName, Map entities, BiFunction buildingFcn) { return entityData -> entityData - .zip(extract(entityData, fieldName, entities)) - .map(enrich(List.of(fieldName), fcn)); + .zip(extractFunction(entityData, fieldName, entities)) + .map(enrichFunction(List.of(fieldName), buildingFcn)); } /** @@ -225,8 +231,8 @@ protected static TryFunction type of entity data * @param type of the first entity * @param type of the second entity @@ -234,35 +240,41 @@ protected static TryFunction - TryFunction biEnrich( + WrappedFunction biEnrich( String fieldName1, Map entities1, String fieldName2, Map entities2, - TriFunction fcn) { + TriFunction buildingFcn) { + // adapting the provided function + BiFunction, R> adaptedBuildingFcn = + (data, pair) -> buildingFcn.apply(data, pair.getKey(), pair.getValue()); + + // extractor to get the needed entities + WrappedFunction> pairExtractor = + data -> + extractFunction(data, fieldName1, entities1) + .zip(extractFunction(data, fieldName2, entities2)); + return entityData -> entityData - .zip( - extract(entityData, fieldName1, entities1) - .zip(extract(entityData, fieldName2, entities2))) - .map( - enrich( - List.of(fieldName1, fieldName2), - (data, pair) -> fcn.apply(data, pair.getKey(), pair.getValue()))); + .zip(pairExtractor) + .map(enrichFunction(List.of(fieldName1, fieldName2), adaptedBuildingFcn)); } /** - * Method to build an {@link EntityData}. + * Method to build a function to create an {@link EntityData}. * * @param fieldNames list with field names - * @param fcn to build the returned {@link EntityData} + * @param buildingFcn to build the returned {@link EntityData} * @return an entity data * @param type of given entity data * @param type of entities * @param type of returned entity data */ - protected static Function, R> enrich( - List fieldNames, BiFunction fcn) { + protected static + Function, R> enrichFunction( + List fieldNames, BiFunction buildingFcn) { return pair -> { E data = pair.getKey(); T entities = pair.getValue(); @@ -272,7 +284,7 @@ protected static Function Stream unpack( * @param type of entity data * @param type of entity */ - protected static Try extract( + protected static Try extractFunction( Try entityData, String fieldName, Map entities) { return entityData.flatMap( data -> @@ -319,7 +331,7 @@ protected static Try extract( + entityData + " failed.", exception)) - .flatMap(entityUuid -> extract(entityUuid, entities))); + .flatMap(entityUuid -> extractFunction(entityUuid, entities))); } /** @@ -330,7 +342,7 @@ protected static Try extract( * @return a try of the {@link Entity} * @param type of entity */ - protected static Try extract(UUID uuid, Map entityMap) { + protected static Try extractFunction(UUID uuid, Map entityMap) { return Optional.ofNullable(entityMap.get(uuid)) // We either find a matching entity for given UUID, thus return a success .map(entity -> Try.of(() -> entity, SourceException.class)) @@ -344,13 +356,13 @@ protected static Try extract(UUID uuid, Map ent // functional interfaces /** - * Adapts the function arguments to a try. + * Wraps the function arguments with a try. * * @param type of first argument * @param type of second argument */ @FunctionalInterface - protected interface TryFunction + protected interface WrappedFunction extends Function, Try> {} /** diff --git a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java index c2ca71510..339f3f638 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/GraphicSource.java @@ -131,7 +131,7 @@ public Set getNodeGraphicInput(Map nodes) /** * If the set of {@link LineInput} entities is not exhaustive for all available {@link * LineGraphicInput} entities or if an error during the building process occurs a {@link - * SourceException} is thrown, else all entities that have been able to be build are returned. + * SourceException} is thrown, else all entities that have been able to be built are returned. */ public Set getLineGraphicInput() throws SourceException { Map operators = typeSource.getOperators(); diff --git a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java index 4ff8a8c0d..46d9432b5 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/RawGridSource.java @@ -342,7 +342,7 @@ public Set get3WTransformers( Map nodes, Map transformer3WTypes) throws SourceException { - TryFunction builder = + WrappedFunction builder = data -> connectorEnricher .andThen( diff --git a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java index 952450fec..72ab4d8b7 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/ResultEntitySource.java @@ -100,10 +100,10 @@ public void validate() throws ValidationException { validate(LineResult.class, dataSource, connectorResultFactory), validate(Transformer2WResult.class, dataSource, connectorResultFactory), validate(Transformer3WResult.class, dataSource, connectorResultFactory), - validate(FlexOptionsResult.class, dataSource, flexOptionsResultFactory)), + validate(FlexOptionsResult.class, dataSource, flexOptionsResultFactory), validate(CongestionResult.class, dataSource, congestionResultFactory))); - Try.scanCollection(participantResults, Void.class) + Try.scanCollection(participantResults, Void.class) .transformF(FailedValidationException::new) .getOrThrow(); } diff --git a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java index dda0b82b8..3df433dac 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/SystemParticipantSource.java @@ -474,7 +474,9 @@ public Set getBmPlants( dataSource, bmInputFactory, data -> - participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + participantEnricher + .andThen(enrichTypes(types)) + .apply(data, operators, nodes, emUnits)) .collect(toSet()); } @@ -527,7 +529,9 @@ public Set getStorages( dataSource, storageInputFactory, data -> - participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + participantEnricher + .andThen(enrichTypes(types)) + .apply(data, operators, nodes, emUnits)) .collect(toSet()); } @@ -578,7 +582,9 @@ public Set getWecPlants( dataSource, wecInputFactory, data -> - participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + participantEnricher + .andThen(enrichTypes(types)) + .apply(data, operators, nodes, emUnits)) .collect(toSet()); } @@ -628,7 +634,9 @@ public Set getEvs( dataSource, evInputFactory, data -> - participantEnricher.andThen(enrich(types)).apply(data, operators, nodes, emUnits)) + participantEnricher + .andThen(enrichTypes(types)) + .apply(data, operators, nodes, emUnits)) .collect(toSet()); } @@ -673,10 +681,10 @@ public Set getChpPlants( Map thermalStorages) throws SourceException { - TryFunction builder = + WrappedFunction builder = data -> participantEnricher - .andThen(enrich(types)) + .andThen(enrichTypes(types)) .andThen( biEnrich( THERMAL_BUS, @@ -725,10 +733,10 @@ public Set getHeatPumps( Map thermalBuses) throws SourceException { - TryFunction builder = + WrappedFunction builder = data -> participantEnricher - .andThen(enrich(types)) + .andThen(enrichTypes(types)) .andThen(enrich(THERMAL_BUS, thermalBuses, HpInputEntityData::new)) .apply(data, operators, nodes, emUnits); return getEntities(HpInput.class, dataSource, hpInputFactory, builder).collect(toSet()); @@ -744,9 +752,9 @@ public Set getHeatPumps( * @param type of types */ private static - TryFunction> enrich(Map types) { - BiFunction> fcn = + WrappedFunction> enrichTypes(Map types) { + BiFunction> typeEnricher = SystemParticipantTypedEntityData::new; - return entityData -> enrich(TYPE, types, fcn).apply(entityData); + return entityData -> enrich(TYPE, types, typeEnricher).apply(entityData); } } diff --git a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java index 26ad09f87..959613b7c 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/WeatherSource.java @@ -120,7 +120,7 @@ protected Map> mapWeatherValuesToPoint * @param inputStream stream of fields to convert into TimeBasedValues * @return a list of that TimeBasedValues */ - public List> buildTimeBasedValues( + protected List> buildTimeBasedValues( TimeBasedWeatherValueFactory factory, Stream> inputStream) throws SourceException { return Try.scanStream( diff --git a/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java index cf22278ea..74732dbb0 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/couchbase/CouchbaseWeatherSource.java @@ -10,7 +10,6 @@ import com.couchbase.client.java.json.JsonObject; import com.couchbase.client.java.kv.GetResult; import com.couchbase.client.java.query.QueryResult; -import edu.ie3.datamodel.exceptions.SourceException; import edu.ie3.datamodel.io.connectors.CouchbaseConnector; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedWeatherValueData; import edu.ie3.datamodel.io.factory.timeseries.TimeBasedWeatherValueFactory; @@ -60,8 +59,7 @@ public CouchbaseWeatherSource( IdCoordinateSource coordinateSource, String coordinateIdColumnName, TimeBasedWeatherValueFactory weatherFactory, - String timeStampPattern) - throws SourceException { + String timeStampPattern) { this( connector, coordinateSource, @@ -175,7 +173,7 @@ public Optional> getWeather(ZonedDateTime date, Poi * @param coordinateId the coordinate Id of the weather data * @return a weather document key */ - public String generateWeatherKey(ZonedDateTime time, Integer coordinateId) { + private String generateWeatherKey(ZonedDateTime time, Integer coordinateId) { String key = keyPrefix + "::"; key += coordinateId + "::"; key += time.format(DateTimeFormatter.ofPattern(timeStampPattern)); @@ -190,7 +188,7 @@ public String generateWeatherKey(ZonedDateTime time, Integer coordinateId) { * @param coordinateId the coordinate ID for which the documents are queried * @return the query string */ - public String createQueryStringForIntervalAndCoordinate( + private String createQueryStringForIntervalAndCoordinate( ClosedInterval timeInterval, int coordinateId) { String basicQuery = "SELECT " + connector.getBucketName() + ".* FROM " + connector.getBucketName(); @@ -232,7 +230,7 @@ private Optional toTimeBasedWeatherValueData(JsonObje * @param jsonObj the JsonObject to convert * @return an optional weather value */ - public Optional> toTimeBasedWeatherValue(JsonObject jsonObj) { + private Optional> toTimeBasedWeatherValue(JsonObject jsonObj) { Optional data = toTimeBasedWeatherValueData(jsonObj); if (data.isEmpty()) { logger.warn("Unable to parse json object"); diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java index 53107906c..7f7e0be78 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java @@ -53,11 +53,6 @@ public CsvIdCoordinateSource(IdCoordinateFactory factory, CsvDataSource dataSour this.factory = factory; this.dataSource = dataSource; - // validating - Try.ofVoid(this::validate, ValidationException.class) - .transformF(SourceException::new) - .getOrThrow(); - /* set up the coordinate id to lat/long mapping */ idToCoordinate = setupIdToCoordinateMap(); coordinateToId = invert(idToCoordinate); diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java index d7de7904a..7fb5d5b02 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvTimeSeriesSource.java @@ -60,8 +60,7 @@ private static CsvTimeSeriesSource create( Path folderPath, FileNamingStrategy fileNamingStrategy, CsvIndividualTimeSeriesMetaInformation metaInformation, - Class valClass) - throws SourceException { + Class valClass) { TimeBasedSimpleValueFactory valueFactory = new TimeBasedSimpleValueFactory<>(valClass); return new CsvTimeSeriesSource<>( csvSep, @@ -91,16 +90,10 @@ public CsvTimeSeriesSource( UUID timeSeriesUuid, Path filePath, Class valueClass, - TimeBasedSimpleValueFactory factory) - throws SourceException { + TimeBasedSimpleValueFactory factory) { super(valueClass, factory); this.dataSource = new CsvDataSource(csvSep, folderPath, fileNamingStrategy); - - // validate this.filePath = filePath; - Try.ofVoid(this::validate, ValidationException.class) - .transformF(SourceException::new) - .getOrThrow(); /* Read in the full time series */ try { diff --git a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java index 6bd6bfff1..0b000bf94 100644 --- a/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java +++ b/src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java @@ -65,11 +65,6 @@ public CsvWeatherSource( super(idCoordinateSource, weatherFactory); this.dataSource = new CsvDataSource(csvSep, folderPath, fileNamingStrategy); - // validating - Try.ofVoid(this::validate, ValidationException.class) - .transformF(SourceException::new) - .getOrThrow(); - coordinateToTimeSeries = getWeatherTimeSeries(); } diff --git a/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java b/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java index f26b409d3..5ef1ae710 100644 --- a/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java +++ b/src/main/java/edu/ie3/datamodel/utils/QuadFunction.java @@ -9,7 +9,7 @@ import java.util.function.Function; /** - * Enhancement of {@link Function} and {@link java.util.function.BiFunction} that accepts three + * Enhancement of {@link Function} and {@link java.util.function.BiFunction} that accepts four * arguments and produces a result. * * @param the type of the first argument to the function diff --git a/src/main/java/edu/ie3/datamodel/utils/Try.java b/src/main/java/edu/ie3/datamodel/utils/Try.java index 5e573c837..ffe006407 100644 --- a/src/main/java/edu/ie3/datamodel/utils/Try.java +++ b/src/main/java/edu/ie3/datamodel/utils/Try.java @@ -284,6 +284,17 @@ public Try, E> zip(Try that) { return zip(that, Pair::of); } + /** + * Method for zipping two tries, where one is created using the data of this try. + * + * @param extractor function to create the second try + * @return a try of a pair. + * @param type of others data + */ + public Try, E> zip(Function, Try> extractor) { + return zip(extractor.apply(this)); + } + /** * Method for zipping two tries. * diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy index 5002c9117..b92fd09a2 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/AssetEntitySourceTest.groovy @@ -103,7 +103,7 @@ class AssetEntitySourceTest extends Specification { def types = map([GridTestData.lineTypeInputCtoD]) when: - def actual = AssetEntitySource.enrich(types).apply(new Try.Success<>(entityData)) + def actual = AssetEntitySource.enrichConnector(types).apply(new Try.Success<>(entityData)) then: actual.success diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy index 6c1e63f82..0c46d0c9a 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/EntitySourceTest.groovy @@ -135,7 +135,7 @@ class EntitySourceTest extends Specification { given: def entityData = new EntityData(["operator": ""], NodeInput) def pair = Pair.of(entityData, GridTestData.profBroccoli) - def fcn = enrich(["operator"], AssetInputEntityData::new) + def fcn = enrichFunction(["operator"], AssetInputEntityData::new) when: def result = fcn.apply(pair) @@ -151,7 +151,7 @@ class EntitySourceTest extends Specification { def entityMap = map([GridTestData.profBroccoli]) when: - def actual = extract(new Try.Success<>(entityData), "operator", entityMap) + def actual = extractFunction(new Try.Success<>(entityData), "operator", entityMap) then: actual.success @@ -163,7 +163,7 @@ class EntitySourceTest extends Specification { def entityData = new EntityData(fieldsToAttributes, NodeInput) when: - def actual = extract(new Try.Success<>(entityData), "operator", entityMap) + def actual = extractFunction(new Try.Success<>(entityData), "operator", entityMap) then: actual.failure @@ -184,7 +184,7 @@ class EntitySourceTest extends Specification { ]) when: - def actual = extract(uuid, entityMap) + def actual = extractFunction(uuid, entityMap) then: actual.failure diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy index 7d9be93e3..aeb9f8829 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/SystemParticipantSourceTest.groovy @@ -42,7 +42,7 @@ class SystemParticipantSourceTest extends Specification { def types = map([sptd.evTypeInput]) when: - def actual = SystemParticipantSource.enrich(types).apply(new Try.Success<>(entityData)) + def actual = SystemParticipantSource.enrichTypes(types).apply(new Try.Success<>(entityData)) then: actual.success From e948db71f60f862e72e8d9865be7bccfbbdbcee1 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 18 Jun 2024 10:39:19 +0000 Subject: [PATCH 10/22] Bump com.couchbase.client:java-client from 3.6.2 to 3.7.0 (#1102) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 019499ddd..20b18cdac 100644 --- a/build.gradle +++ b/build.gradle @@ -90,7 +90,7 @@ dependencies { // Databases implementation 'org.influxdb:influxdb-java:2.24' - implementation 'com.couchbase.client:java-client:3.6.2' + implementation 'com.couchbase.client:java-client:3.7.0' runtimeOnly 'org.postgresql:postgresql:42.7.3' // postgresql jdbc driver required during runtime implementation 'commons-io:commons-io:2.16.1' // I/O functionalities From e22ee4d2093b573f6eadf04465846b488715a31d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 19 Jun 2024 07:47:32 +0000 Subject: [PATCH 11/22] Bump com.github.spotbugs from 6.0.16 to 6.0.17 (#1101) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 20b18cdac..090d2f482 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '6.25.0' //code format - id 'com.github.spotbugs' version '6.0.16' // code check, working on byte code + id 'com.github.spotbugs' version '6.0.17' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From c44b0479b41a23c942a97fe646a2dece9cd1ee0f Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Jun 2024 09:22:01 +0200 Subject: [PATCH 12/22] Implementing requested changes. --- .../csv/CsvSystemParticipantSourceTest.groovy | 68 +++++++++---------- .../edu/ie3/datamodel/utils/TryTest.groovy | 56 +++++++++++++++ 2 files changed, 89 insertions(+), 35 deletions(-) diff --git a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy index 43e4812d3..67316ab30 100644 --- a/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/io/source/csv/CsvSystemParticipantSourceTest.groovy @@ -156,11 +156,10 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat heatPumps.exception.get().class == SourceException where: - operators | types | thermalBuses || resultingSize | resultingSet - [] | [] | [] || 0 | [] - [] | [] | [] || 0 | [] - [sptd.hpInput.operator] | [] | [] || 0 | [] - [sptd.hpInput.operator] | [sptd.hpInput.type] | [] || 0 | [] + operators | types | thermalBuses + [] | [] | [] + [sptd.hpInput.operator] | [] | [] + [sptd.hpInput.operator] | [sptd.hpInput.type] | [] } def "A SystemParticipantSource with csv input should throw an exception from a invalid chp input file as expected"() { @@ -181,11 +180,11 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat chpUnits.exception.get().class == SourceException where: - operators | types | thermalBuses | thermalStorages || resultingSet - [] | [] | [] | [] as List || [] - [] | [] | [] | [] as List || [] - [sptd.chpInput.operator] | [] | [] | [] as List || [] - [sptd.chpInput.operator] | [sptd.chpInput.type] | [] | [] as List || [] + operators | types | thermalBuses | thermalStorages + [] | [] | [] | [] as List + [] | [] | [] | [] as List + [sptd.chpInput.operator] | [] | [] | [] as List + [sptd.chpInput.operator] | [sptd.chpInput.type] | [] | [] as List } def "A SystemParticipantSource with csv input should throw an exception from invalid ev input file as expected"() { @@ -206,9 +205,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - operators | types || resultingSet - [sptd.evInput.operator] | [] || [] - [] | [] || [] + operators | types + [sptd.evInput.operator] | [] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid wec input file as expected"() { @@ -229,9 +228,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - operators | types || resultingSet - [sptd.wecInput.operator] | [] || [] - [] | [] || [] + operators | types + [sptd.wecInput.operator] | [] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid storage input file as expected"() { @@ -252,9 +251,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - operators | types || resultingSet - [sptd.storageInput.operator] | [] || [] - [] | [] || [] + operators | types + [sptd.storageInput.operator] | [] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid bm input file as expected"() { @@ -275,10 +274,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - operators | types || resultingSet - [sptd.bmInput.operator] | [] || [] - [] | [] || [] - [] | [] || [] + operators | types + [sptd.bmInput.operator] | [] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid ev charging station input file as expected"() { @@ -298,9 +296,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - nodes | operators || resultingSet - [] | [sptd.evcsInput.operator] || [] - [] | [] || [] + nodes | operators + [] | [sptd.evcsInput.operator] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid load input file as expected"() { @@ -320,9 +318,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - nodes | operators || resultingSet - [] | [sptd.loadInput.operator] || [] - [] | [] || [] + nodes | operators + [] | [sptd.loadInput.operator] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid pv input file as expected"() { @@ -342,9 +340,9 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - nodes | operators || resultingSet - [] | [sptd.pvInput.operator] || [] - [] | [] || [] + nodes | operators + [] | [sptd.pvInput.operator] + [] | [] } def "A SystemParticipantSource with csv input should throw an exception from invalid fixedFeedIn input file as expected"() { @@ -364,8 +362,8 @@ class CsvSystemParticipantSourceTest extends Specification implements CsvTestDat sysParts.exception.get().class == SourceException where: - nodes | operators || resultingSet - [] | [sptd.fixedFeedInInput.operator] || [] - [] | [] || [] + nodes | operators + [] | [sptd.fixedFeedInInput.operator] + [] | [] } } diff --git a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy index 1419f04f3..54cb976ba 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy @@ -8,8 +8,10 @@ package edu.ie3.datamodel.utils import edu.ie3.datamodel.exceptions.FailureException import edu.ie3.datamodel.exceptions.SourceException import edu.ie3.datamodel.exceptions.TryException +import org.apache.commons.lang3.tuple.Pair import spock.lang.Specification +import java.util.function.Function import java.util.stream.Stream class TryTest extends Specification { @@ -343,6 +345,60 @@ class TryTest extends Specification { flatMapF.exception.get() == failure.get() } + def "Two Tries can be zipped correctly"() { + given: + def given = Try.of(() -> 1, SourceException) + + when: + def actual = given.zip(second) + + then: + actual.isSuccess() == isSuccess + actual.data == expectedData + + where: + second | isSuccess | expectedData + new Try.Failure(new SourceException("source exception")) | false | Optional.empty() + new Try.Success("1") | true | Optional.of(Pair.of(1, "1")) + } + + def "Two Tries can be zipped to any object correctly"() { + given: + def given = Try.of(() -> 1, SourceException) + def fcn = (i1, i2) -> i1 + i2 + + when: + def actual = given.zip(second, fcn) + + then: + actual.isSuccess() == isSuccess + actual.data == expectedData + + where: + second | isSuccess | expectedData + new Try.Failure(new SourceException("source exception")) | false | Optional.empty() + new Try.Success(10) | true | Optional.of(11) + } + + def "Two Tries can be extracted and zipped correctly"() { + given: + def given = Try.of(() -> 1, SourceException) + + def failureExtractor = f -> new Try.Failure(new SourceException("source exception")) + def successExtractor = s -> new Try.Success("10") + + when: + def failure = given.zip(failureExtractor) + def success = given.zip(successExtractor) + + then: + failure.isFailure() + + success.isSuccess() + success.data == Optional.of(Pair.of(1, "10")) + } + + def "The convert method should work correctly for successes"() { given: Try, SourceException> success = new Try.Success(Stream.of(1, 2, 3)) From 01eef22dc215c8a97e7bf406f6a7f32c0b117deb Mon Sep 17 00:00:00 2001 From: staudtMarius Date: Mon, 24 Jun 2024 11:03:31 +0200 Subject: [PATCH 13/22] Fixing project being build twice in CI --- CHANGELOG.md | 1 + Jenkinsfile | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6fc54cd70..eeee0f599 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed `MappingEntryies` not getting processed by adding `Getter` methods for record fields [#1084](https://github.com/ie3-institute/PowerSystemDataModel/issues/1084) - Fixed "depth of discharge" in documentation [#872](https://github.com/ie3-institute/PowerSystemDataModel/issues/872) +- Fixed project beiing build twice in CI [#994](https://github.com/ie3-institute/PowerSystemDataModel/issues/994) ### Changed - Improvements to the search for corner points in `IdCoordinateSource` [#1016](https://github.com/ie3-institute/PowerSystemDataModel/issues/1016) diff --git a/Jenkinsfile b/Jenkinsfile index 1a6029879..ea043dbd5 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -113,7 +113,7 @@ node { gradle('--refresh-dependencies clean spotlessCheck pmdMain pmdTest spotbugsMain ' + 'spotbugsTest test jacocoTestReport jacocoTestCoverageVerification', projectName) - sh(script: """set +x && cd $projectName""" + ''' set +x; ./gradlew clean javadoc''', returnStdout: true) + sh(script: """set +x && cd $projectName""" + ''' set +x; ./gradlew javadoc''', returnStdout: true) } // sonarqube analysis @@ -156,7 +156,7 @@ node { */ sh( script: """set +x && cd $projectName""" + - ''' set +x; ./gradlew clean javadoc''', + ''' set +x; ./gradlew javadoc''', returnStdout: true ) From 227ce3310c0c087d3b53109c7b1cf18bb6efb255 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Jun 2024 10:25:52 +0000 Subject: [PATCH 14/22] Bump com.github.spotbugs from 6.0.17 to 6.0.18 (#1103) --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 090d2f482..3a97a0b6a 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ plugins { id 'signing' id 'pmd' // code check, working on source code id 'com.diffplug.spotless' version '6.25.0' //code format - id 'com.github.spotbugs' version '6.0.17' // code check, working on byte code + id 'com.github.spotbugs' version '6.0.18' // code check, working on byte code id 'de.undercouch.download' version '5.6.0' id 'kr.motd.sphinx' version '2.10.1' // documentation generation id 'jacoco' // java code coverage plugin From 756f80754734a7217d31495884eadee13a5bacf3 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 24 Jun 2024 12:28:32 +0200 Subject: [PATCH 15/22] Removing unused import --- src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy index 54cb976ba..b7d636a15 100644 --- a/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy +++ b/src/test/groovy/edu/ie3/datamodel/utils/TryTest.groovy @@ -11,7 +11,6 @@ import edu.ie3.datamodel.exceptions.TryException import org.apache.commons.lang3.tuple.Pair import spock.lang.Specification -import java.util.function.Function import java.util.stream.Stream class TryTest extends Specification { From 5526887fb1cf635c3e73b1eb3cb8fcf5b0101d24 Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 24 Jun 2024 12:43:58 +0200 Subject: [PATCH 16/22] Setting version --- version.properties | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/version.properties b/version.properties index cd5fdc78c..f9c849a07 100644 --- a/version.properties +++ b/version.properties @@ -1,8 +1,8 @@ #Generated by the Semver Plugin for Gradle -#Thu Mar 07 09:02:16 CET 2024 +#Mon Jun 24 12:43:47 CEST 2024 version.buildmeta= version.major=5 -version.minor=0 -version.patch=1 +version.minor=1 +version.patch=0 version.prerelease= -version.semver=5.0.1 +version.semver=5.1.0 From 051c9f502b8001c930fbc52154d0f7658f26e91a Mon Sep 17 00:00:00 2001 From: Sebastian Peter Date: Mon, 24 Jun 2024 12:45:14 +0200 Subject: [PATCH 17/22] Updating gradle to 8.8 --- gradle/wrapper/gradle-wrapper.jar | Bin 63721 -> 43462 bytes gradle/wrapper/gradle-wrapper.properties | 2 +- gradlew.bat | 20 ++++++++++---------- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7f93135c49b765f8051ef9d0a6055ff8e46073d8..d64cd4917707c1f8861d8cb53dd15194d4248596 100644 GIT binary patch literal 43462 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