Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
19e54df
added empty class of InputEntityProcessor
johanneshiry Mar 23, 2020
31b9b82
fix InputDatamodelConcept.puml Operable
johanneshiry Mar 24, 2020
062dbb6
AssetInputProcessor with NodeInput processing capabilities
johanneshiry Mar 24, 2020
7561efd
support for 2W- and 3W-Transformers in AssetInputProcessor
johanneshiry Mar 24, 2020
1a3da73
change transformer wiring in GridTestData according to the transforme…
johanneshiry Mar 24, 2020
7bf078c
added support for all ConnectorInput entities in EntityProcessor
johanneshiry Mar 24, 2020
456258d
added note for thermalBus in ChpInput
johanneshiry Mar 25, 2020
5a0c7ce
extended BdewLoadProfile with method to get a key from a string + ada…
johanneshiry Mar 25, 2020
3539598
new constructors for LoadInput to pass over a string for construction…
johanneshiry Mar 25, 2020
a827cce
finished SystemParticipantTestData + fmt
johanneshiry Mar 25, 2020
021ae29
finished AssetInputProcessor + corresponding test
johanneshiry Mar 25, 2020
287624d
removed debug printer from AssetInputProcessorTest
johanneshiry Mar 25, 2020
fb1184b
added documenting comment
johanneshiry Mar 25, 2020
e886d57
addressing final todo comments
johanneshiry Mar 25, 2020
b9843ca
Merge branch 'master' into jh/#73-extend-processors-and-csv-file-sink
ckittl Mar 25, 2020
47df288
Add AssetInputProcessor to ProcessorProvider
ckittl Mar 25, 2020
4f3c118
Formatting (-:
ckittl Mar 25, 2020
2fa9bf8
Fixing tests
ckittl Mar 25, 2020
bcb7b12
Update src/test/groovy/edu/ie3/datamodel/io/processor/input/AssetInpu…
johanneshiry Mar 26, 2020
123e3ba
addressing some reviewers comments
johanneshiry Mar 26, 2020
a16abad
Update src/test/groovy/edu/ie3/datamodel/io/processor/input/AssetInpu…
johanneshiry Mar 26, 2020
3ce4dad
Merge branch 'jh/#73-extend-processors-and-csv-file-sink' of github.c…
johanneshiry Mar 26, 2020
b123e2f
addressing some reviewers comments + fmt
johanneshiry Mar 26, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions docs/uml/InputDatamodelConcept.puml
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,7 @@ abstract Class InputEntity
InputEntity --|> UniqueEntity

Interface Operable {
+ operationInterval: Interval<ZonedDateTime>
+ operatesFrom: ZonedDateTime
+ operatesUntil: ZonedDateTime
+ inOperation: Boolean
+ operator: OperatorInput
}

abstract Class AssetInput {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,10 @@

public class NodeInputFactory extends AssetInputEntityFactory<NodeInput, AssetInputEntityData> {
private static final String V_TARGET = "vtarget";
private static final String V_RATED = "vrated";
public static final String V_RATED = "vrated";
private static final String SLACK = "slack";
private static final String GEO_POSITION = "geoposition";
private static final String VOLT_LVL = "voltlvl";
public static final String VOLT_LVL = "voltlvl";
private static final String SUBNET = "subnet";

public NodeInputFactory() {
Expand Down
247 changes: 228 additions & 19 deletions src/main/java/edu/ie3/datamodel/io/processor/EntityProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,23 +6,29 @@
package edu.ie3.datamodel.io.processor;

import edu.ie3.datamodel.exceptions.EntityProcessorException;
import edu.ie3.datamodel.io.factory.input.NodeInputFactory;
import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor;
import edu.ie3.datamodel.models.OperationTime;
import edu.ie3.datamodel.models.StandardLoadProfile;
import edu.ie3.datamodel.models.StandardUnits;
import edu.ie3.datamodel.models.UniqueEntity;
import edu.ie3.datamodel.models.input.system.StorageStrategy;
import edu.ie3.datamodel.models.voltagelevels.VoltageLevel;
import edu.ie3.util.TimeTools;
import edu.ie3.util.quantities.interfaces.EnergyPrice;
import java.beans.Introspector;
import java.lang.reflect.Method;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.util.*;
import java.util.stream.Collectors;
import javax.measure.Quantity;
import javax.measure.quantity.Dimensionless;
import javax.measure.quantity.ElectricCurrent;
import javax.measure.quantity.Power;
import javax.measure.quantity.*;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.io.geojson.GeoJsonWriter;

/**
* Internal API Interface for EntityProcessors. Main purpose is to 'de-serialize' models into a
Expand All @@ -37,10 +43,20 @@ public abstract class EntityProcessor<T extends UniqueEntity> {
public final Logger log = LogManager.getLogger(this.getClass());
private final Class<? extends T> registeredClass;
protected final String[] headerElements;
protected final LinkedHashMap<String, Method> fieldNameToMethod = new LinkedHashMap<>();
private final Map<String, Method> fieldNameToMethod;

private static final String OPERATION_TIME_FIELD_NAME = OperationTime.class.getSimpleName();
private static final String OPERATES_FROM = "operatesFrom";
private static final String OPERATES_UNTIL = "operatesUntil";

private static final String VOLT_LVL_FIELD_NAME = "voltLvl";
private static final String VOLT_LVL = NodeInputFactory.VOLT_LVL;
private static final String V_RATED = NodeInputFactory.V_RATED;

private static final GeoJsonWriter geoJsonWriter = new GeoJsonWriter();

/** Field name of {@link UniqueEntity} uuid */
private static final String uuidString = "uuid";
private static final String UUID_FIELD_NAME = "uuid";

/**
* Create a new EntityProcessor
Expand All @@ -49,7 +65,15 @@ public abstract class EntityProcessor<T extends UniqueEntity> {
*/
public EntityProcessor(Class<? extends T> registeredClass) {
this.registeredClass = registeredClass;
this.headerElements = registerClass(registeredClass, getAllEligibleClasses());
this.fieldNameToMethod = registerClass(registeredClass, getAllEligibleClasses());
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partly this is not only caused by this PR, but I would appreciate, that you point out more in detail, what an eligible class distinguishes from a _registered_class. Moreover, the naming registeredClass is misleading from my point of view, as it is not registered yet, but foreseen to be registered.

this.headerElements =
ArrayUtils
.addAll( // ensures that uuid is always the first entry in the header elements array
new String[] {UUID_FIELD_NAME},
fieldNameToMethod.keySet().stream()
.filter(x -> !x.toLowerCase().contains(UUID_FIELD_NAME))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
.filter(x -> !x.toLowerCase().contains(UUID_FIELD_NAME))
.filter(x -> !x.toLowerCase().contains(UUID_FIELD_NAME).toLowerCase())

.toArray(String[]::new));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the next line: Didn't we agree on ISO time strings?

TimeTools.initialize(ZoneId.of("UTC"), Locale.GERMANY, "yyyy-MM-dd HH:mm:ss");
}

Expand All @@ -59,7 +83,11 @@ public EntityProcessor(Class<? extends T> registeredClass) {
* @param cls class to be registered
* @return an array of strings of all field values of the registered class
*/
private String[] registerClass(Class<? extends T> cls, List<Class<? extends T>> eligibleClasses) {
private Map<String, Method> registerClass(
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

From my point of view, the naming of the class is not suitable, as it is not registering the class, but is preparing the field to method mapping.

Class<? extends T> cls, List<Class<? extends T>> eligibleClasses) {

final LinkedHashMap<String, Method> resFieldNameToMethod = new LinkedHashMap<>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you use a linked hash map (applies to different other places)? I think there is no necessity for it, is it?


if (!eligibleClasses.contains(cls))
throw new EntityProcessorException(
"Cannot register class '"
Expand All @@ -77,21 +105,28 @@ private String[] registerClass(Class<? extends T> cls, List<Class<? extends T>>
.forEach(
pd -> { // invoke method to get value
if (pd.getReadMethod() != null) {
fieldNameToMethod.put(pd.getName(), pd.getReadMethod());

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For the if condition. I think this is still filtered, isn't it? (See filter statement before the foreach)

// OperationTime needs to be replaced by operatesFrom and operatesUntil
String fieldName = pd.getName();
if (fieldName.equalsIgnoreCase(OPERATION_TIME_FIELD_NAME)) {
fieldName = OPERATES_FROM;
resFieldNameToMethod.put(OPERATES_UNTIL, pd.getReadMethod());
}

// VoltageLevel needs to be replaced by id and nominalVoltage
if (fieldName.equalsIgnoreCase(VOLT_LVL_FIELD_NAME)) {
fieldName = V_RATED;
resFieldNameToMethod.put(VOLT_LVL, pd.getReadMethod());
}
resFieldNameToMethod.put(fieldName, pd.getReadMethod());
}
});

} catch (Exception e) {
throw new EntityProcessorException(
"Error during EntityProcessor class registration process. Exception was:" + e);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"Error during EntityProcessor class registration process. Exception was:" + e);
"Error during EntityProcessor class registration process.", e);

}

// uuid should always be the first element in the map
String[] filteredArray =
fieldNameToMethod.keySet().stream()
.filter(x -> !x.toLowerCase().contains(uuidString))
.toArray(String[]::new);
return ArrayUtils.addAll(new String[] {uuidString}, filteredArray);
return Collections.unmodifiableMap(resFieldNameToMethod);
}

/**
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please alter the exception message. It is suggested to use a different factory, but in fact it has to be a different processor.

Expand All @@ -115,14 +150,117 @@ public Optional<LinkedHashMap<String, String>> handleEntity(T entity) {
}

/**
* Actual implementation of the handling process. Depends on the entity that should be processed
* and hence needs to be implemented individually
* Actual implementation of the entity handling process
*
* @param entity the entity that should be 'de-serialized' into a map of fieldName -> fieldValue
* @return an optional Map with fieldName -> fieldValue or an empty optional if an error occurred
* during processing
*/
protected abstract Optional<LinkedHashMap<String, String>> processEntity(T entity);
private Optional<LinkedHashMap<String, String>> processEntity(T entity) {

Optional<LinkedHashMap<String, String>> resultMapOpt;

try {
LinkedHashMap<String, String> resultMap = new LinkedHashMap<>();
for (String fieldName : headerElements) {
Method method = fieldNameToMethod.get(fieldName);
Optional<Object> methodReturnObjectOpt = Optional.ofNullable(method.invoke(entity));

if (methodReturnObjectOpt.isPresent()) {
resultMap.put(
fieldName, processMethodResult(methodReturnObjectOpt.get(), method, fieldName));
} else {
resultMap.put(fieldName, "");
}
}
resultMapOpt = Optional.of(resultMap);
} catch (Exception e) {
log.error("Error during entity processing:", e);
resultMapOpt = Optional.empty();
}
return resultMapOpt;
}

private String processMethodResult(Object methodReturnObject, Method method, String fieldName) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaDoc, por favor!


StringBuilder resultStringBuilder = new StringBuilder();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why a string builder? As far as I understand, you could also use a simple String here, as there something only appended once.


switch (method.getReturnType().getSimpleName()) {
// primitives (Boolean, Character, Byte, Short, Integer, Long, Float, Double, String,
case "UUID":
case "boolean":
case "int":
case "double":
case "String":
resultStringBuilder.append(methodReturnObject.toString());
break;
case "Quantity":
resultStringBuilder.append(
handleQuantity((Quantity<?>) methodReturnObject, fieldName)
.orElseThrow(
() ->
new EntityProcessorException(
"Unable to process quantity value for attribute '"
+ fieldName
+ "' in result entity "
+ getRegisteredClass().getSimpleName()
+ ".class.")));
break;
case "ZonedDateTime":
resultStringBuilder.append(processZonedDateTime((ZonedDateTime) methodReturnObject));
break;
case "OperationTime":
resultStringBuilder.append(
processOperationTime((OperationTime) methodReturnObject, fieldName));
break;
case "VoltageLevel":
resultStringBuilder.append(
processVoltageLevel((VoltageLevel) methodReturnObject, fieldName));
break;
case "Point":
case "LineString":
resultStringBuilder.append(geoJsonWriter.write((Geometry) methodReturnObject));
break;
case "StandardLoadProfile":
resultStringBuilder.append(((StandardLoadProfile) methodReturnObject).getKey());
break;
case "StorageStrategy":
resultStringBuilder.append(((StorageStrategy) methodReturnObject).getToken());
break;
case "NodeInput":
case "Transformer3WTypeInput":
case "Transformer2WTypeInput":
case "LineTypeInput":
case "OperatorInput":
case "WecTypeInput":
case "ThermalBusInput":
case "ThermalStorageInput":
case "ChpTypeInput":
case "BmTypeInput":
case "EvTypeInput":
case "StorageTypeInput":
case "HpTypeInput":
resultStringBuilder.append(((UniqueEntity) methodReturnObject).getUuid());
break;
case "Optional": // todo needs to be removed asap as this is very dangerous, but necessary as
// long as #75 is not addressed
resultStringBuilder.append(((Optional<String>) methodReturnObject).orElse(""));
break;
default:
throw new EntityProcessorException(
"Unable to process value for attribute/field '"
+ fieldName
+ "' and method return type '"
+ method.getReturnType().getSimpleName()
+ "' for method with name '"
+ method.getName()
+ "' in in entity model "
+ getRegisteredClass().getSimpleName()
+ ".class.");
}

return resultStringBuilder.toString();
}

/**
* Standard method to process a ZonedDateTime to a String based on a method return object NOTE:
Expand All @@ -136,6 +274,59 @@ protected String processZonedDateTime(ZonedDateTime zonedDateTime) {
return TimeTools.toString(zonedDateTime);
}

/**
* Handling of elements of type {@link OperationTime}
*
* @param operationTime the operation time that should be processed
* @param fieldName the field name that should be generated (either operatesFrom or operatesUntil)
* @return the resulting string of a OperationTime attribute value for the provided field or an
* empty string when an invalid field name is provided
*/
protected String processOperationTime(OperationTime operationTime, String fieldName) {
StringBuilder resultStringBuilder = new StringBuilder();

if (fieldName.equalsIgnoreCase(OPERATES_FROM))
operationTime
.getStartDate()
.ifPresent(startDate -> resultStringBuilder.append(processZonedDateTime(startDate)));

if (fieldName.equalsIgnoreCase(OPERATES_UNTIL))
operationTime
.getEndDate()
.ifPresent(endDate -> resultStringBuilder.append(processZonedDateTime(endDate)));

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What if the field name is not reasonable? Maybe throw an exception instead of just returning ""?

return resultStringBuilder.toString();
}

/**
* Handling of elements of type {@link VoltageLevel}
*
* @param voltageLevel the voltage level that should be processed
* @param fieldName the field name that should be generated (either v_rated or volt_lvl)
* @return the resulting string of a VoltageLevel attribute value for the provided field or an
* empty string when an invalid field name is provided
*/
protected String processVoltageLevel(VoltageLevel voltageLevel, String fieldName) {

StringBuilder resultStringBuilder = new StringBuilder();
if (fieldName.equalsIgnoreCase(VOLT_LVL)) resultStringBuilder.append(voltageLevel.getId());

if (fieldName.equalsIgnoreCase(V_RATED))
resultStringBuilder.append(
handleQuantity(voltageLevel.getNominalVoltage(), fieldName)
.orElseThrow(
() ->
new EntityProcessorException(
"Unable to process quantity value for attribute '"
+ fieldName
+ "' in result entity "
+ getRegisteredClass().getSimpleName()
+ ".class.")));
;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lost ;. 😭


return resultStringBuilder.toString();
}

/**
* Standard method to process a Quantity to a String based on a method return object
*
Expand All @@ -152,6 +343,10 @@ protected Optional<String> handleQuantity(Quantity<?> quantity, String fieldName
case "p":
case "q":
case "energy":
case "vTarget":
case "vrated":
case "sRated":
case "eConsAnnual":
normalizedQuantityValue = handleProcessorSpecificQuantity(quantity, fieldName);
break;
case "soc":
Expand All @@ -160,6 +355,9 @@ protected Optional<String> handleQuantity(Quantity<?> quantity, String fieldName
case "iAAng":
case "iBAng":
case "iCAng":
case "etaConv":
case "azimuth":
case "height":
normalizedQuantityValue = quantityValToOptionalString(quantity);
break;
case "iAMag":
Expand All @@ -181,9 +379,20 @@ protected Optional<String> handleQuantity(Quantity<?> quantity, String fieldName
quantityValToOptionalString(
quantity.asType(Dimensionless.class).to(StandardUnits.FILL_LEVEL));
break;
case "length":
normalizedQuantityValue =
quantityValToOptionalString(
quantity.asType(Length.class).to(StandardUnits.LINE_LENGTH));
break;

case "feedInTariff":
normalizedQuantityValue =
quantityValToOptionalString(
quantity.asType(EnergyPrice.class).to(StandardUnits.ENERGY_PRICE));
break;
default:
log.error(
"Cannot process quantity {} for field with name {} in model processing!",
"Cannot process quantity with value '{}' for field with name {} in model processing!",
quantity,
fieldName);
break;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
package edu.ie3.datamodel.io.processor;

import edu.ie3.datamodel.exceptions.ProcessorProviderException;
import edu.ie3.datamodel.io.processor.input.AssetInputProcessor;
import edu.ie3.datamodel.io.processor.result.ResultEntityProcessor;
import edu.ie3.datamodel.models.UniqueEntity;
import edu.ie3.datamodel.models.input.AssetInput;
import edu.ie3.datamodel.models.result.ResultEntity;
import java.util.*;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -128,6 +130,10 @@ private Collection<EntityProcessor<? extends UniqueEntity>> allProcessors() {
Collection<EntityProcessor<? extends UniqueEntity>> resultingProcessors = new ArrayList<>();

// todo add missing processors here
// AssetInput
for (Class<? extends AssetInput> cls : AssetInputProcessor.eligibleEntityClasses) {
resultingProcessors.add(new AssetInputProcessor(cls));
}

// SystemParticipantResults
for (Class<? extends ResultEntity> cls : ResultEntityProcessor.eligibleEntityClasses) {
Expand Down
Loading