-
Notifications
You must be signed in to change notification settings - Fork 7
Extend processors and csv file sink #74
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
19e54df
31b9b82
062dbb6
7561efd
1a3da73
7bf078c
456258d
5a0c7ce
3539598
a827cce
021ae29
287624d
fb1184b
e886d57
b9843ca
47df288
4f3c118
2fa9bf8
bcb7b12
123e3ba
a16abad
3ce4dad
b123e2f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -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 | ||||||
|
@@ -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; | ||||||
johanneshiry marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
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"; | ||||||
johanneshiry marked this conversation as resolved.
Show resolved
Hide resolved
|
||||||
|
||||||
/** | ||||||
* Create a new EntityProcessor | ||||||
|
@@ -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()); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 |
||||||
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)) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
.toArray(String[]::new)); | ||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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"); | ||||||
} | ||||||
|
||||||
|
@@ -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( | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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<>(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 '" | ||||||
|
@@ -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()); | ||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
} | ||||||
|
||||||
// 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); | ||||||
} | ||||||
|
||||||
/** | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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. |
||||||
|
@@ -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) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. JavaDoc, por favor! |
||||||
|
||||||
StringBuilder resultStringBuilder = new StringBuilder(); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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: | ||||||
|
@@ -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))); | ||||||
|
||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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."))); | ||||||
; | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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 | ||||||
* | ||||||
|
@@ -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": | ||||||
|
@@ -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": | ||||||
|
@@ -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; | ||||||
|
Uh oh!
There was an error while loading. Please reload this page.