Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
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
37 changes: 21 additions & 16 deletions src/main/java/edu/ie3/datamodel/io/factory/EntityData.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import static edu.ie3.util.quantities.PowerSystemUnits.KILOVOLT;

import edu.ie3.datamodel.exceptions.FactoryException;
import edu.ie3.datamodel.exceptions.SourceException;
import edu.ie3.datamodel.exceptions.VoltageLevelException;
import edu.ie3.datamodel.models.Entity;
import edu.ie3.datamodel.models.voltagelevels.GermanVoltageLevelUtils;
Expand Down Expand Up @@ -45,6 +45,10 @@ public EntityData(Map<String, String> fieldsToAttributes, Class<? extends Entity
super(fieldsToAttributes, entityClass);
}

public EntityData(Map<String, String> fieldsToAttributes) {
super(fieldsToAttributes, null);
}

/**
* Creates a new EntityData object based on a given {@link FactoryData} object
*
Expand All @@ -61,35 +65,35 @@ public Class<? extends Entity> getTargetClass() {
}

/**
* Returns boolean value for given field name. Throws {@link FactoryException} if field does not
* Returns boolean value for given field name. Throws {@link SourceException} if field does not
* exist, or field value is null or empty.
*
* @param field field name
* @return true if value is "1" or "true", false otherwise
*/
public boolean getBoolean(String field) {
public boolean getBoolean(String field) throws SourceException {
final String value = getField(field);

if (value == null || value.trim().isEmpty())
throw new FactoryException(String.format("Field \"%s\" is null or empty", field));
throw new SourceException(String.format("Field \"%s\" is null or empty", field));

return value.trim().equals("1") || value.trim().equalsIgnoreCase("true");
}

/**
* Parses and returns a geometry from field value of given field name. Throws {@link
* FactoryException} if field does not exist or parsing fails.
* SourceException} if field does not exist or parsing fails.
*
* @param field field name
* @return Geometry if field value is not empty, empty Optional otherwise
*/
private Optional<Geometry> getGeometry(String field) {
private Optional<Geometry> getGeometry(String field) throws SourceException {
String value = getField(field);
try {
if (value.trim().isEmpty()) return Optional.empty();
else return Optional.of(geoJsonReader.read(value));
} catch (ParseException pe) {
throw new FactoryException(
throw new SourceException(
String.format(
"Exception while trying to parse geometry of field \"%s\" with value \"%s\"",
field, value),
Expand All @@ -99,17 +103,17 @@ private Optional<Geometry> getGeometry(String field) {

/**
* Parses and returns a geometrical LineString from field value of given field name. Throws {@link
* FactoryException} if field does not exist or parsing fails.
* SourceException} if field does not exist or parsing fails.
*
* @param field field name
* @return LineString if field value is not empty, empty Optional otherwise
*/
public Optional<LineString> getLineString(String field) {
public Optional<LineString> getLineString(String field) throws SourceException {
Optional<Geometry> geom = getGeometry(field);
if (geom.isPresent()) {
if (geom.get() instanceof LineString lineString) return Optional.of(lineString);
else
throw new FactoryException(
throw new SourceException(
"Geometry is of type "
+ geom.getClass().getSimpleName()
+ ", but type LineString is required");
Expand All @@ -118,17 +122,17 @@ public Optional<LineString> getLineString(String field) {

/**
* Parses and returns a geometrical Point from field value of given field name. Throws {@link
* FactoryException} if field does not exist or parsing fails.
* SourceException} if field does not exist or parsing fails.
*
* @param field field name
* @return Point if field value is not empty, empty Optional otherwise
*/
public Optional<Point> getPoint(String field) {
public Optional<Point> getPoint(String field) throws SourceException {
Optional<Geometry> geom = getGeometry(field);
if (geom.isPresent()) {
if (geom.get() instanceof Point point) return Optional.of(point);
else
throw new FactoryException(
throw new SourceException(
"Geometry is of type "
+ geom.getClass().getSimpleName()
+ ", but type Point is required");
Expand All @@ -137,20 +141,21 @@ public Optional<Point> getPoint(String field) {

/**
* Parses and returns a voltage level from field value of given field name. Throws {@link
* FactoryException} if field does not exist or parsing fails.
* SourceException} if field does not exist or parsing fails.
*
* @param voltLvlField name of the field containing the voltage level
* @param ratedVoltField name of the field containing the rated voltage
* @return Voltage level
*/
public VoltageLevel getVoltageLvl(String voltLvlField, String ratedVoltField) {
public VoltageLevel getVoltageLvl(String voltLvlField, String ratedVoltField)
throws SourceException {
try {
final String voltLvlId = getField(voltLvlField);
final ComparableQuantity<ElectricPotential> vRated = getQuantity(ratedVoltField, KILOVOLT);

return parseToGermanVoltLvlOrIndividual(voltLvlId, vRated);
} catch (IllegalArgumentException iae) {
throw new FactoryException("VoltageLevel could not be parsed", iae);
throw new SourceException("VoltageLevel could not be parsed", iae);
}
}

Expand Down
160 changes: 5 additions & 155 deletions src/main/java/edu/ie3/datamodel/io/factory/Factory.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,10 @@
package edu.ie3.datamodel.io.factory;

import edu.ie3.datamodel.exceptions.FactoryException;
import edu.ie3.datamodel.exceptions.FailedValidationException;
import edu.ie3.datamodel.exceptions.ValidationException;
import edu.ie3.datamodel.exceptions.SourceException;
import edu.ie3.datamodel.io.source.SourceValidator;
import edu.ie3.datamodel.utils.Try;
import edu.ie3.datamodel.utils.Try.*;
import edu.ie3.util.StringUtils;
import java.util.*;
import java.util.stream.Collectors;
import org.slf4j.Logger;
Expand All @@ -26,7 +24,8 @@
* @param <R> Type of the intended return type (might differ slightly from target class (cf. {@link
* edu.ie3.datamodel.io.factory.timeseries.TimeBasedValueFactory})).
*/
public abstract class Factory<C, D extends FactoryData, R> implements SourceValidator<C> {
@Deprecated
public abstract class Factory<C, D extends FactoryData, R> extends SourceValidator<C> {
public static final Logger log = LoggerFactory.getLogger(Factory.class);

private final List<Class<? extends C>> supportedClasses;
Expand Down Expand Up @@ -54,7 +53,7 @@ public Try<R, FactoryException> get(D data) {
try {
// build the model
return Success.of(buildModel(data));
} catch (FactoryException | IllegalArgumentException e) {
} catch (FactoryException | SourceException | IllegalArgumentException e) {
return Failure.of(
new FactoryException(
"An error occurred when creating instance of "
Expand Down Expand Up @@ -84,7 +83,7 @@ public Try<R, FactoryException> get(Try<D, ?> data) {
* @return model created from data
* @throws FactoryException if the model cannot be build
*/
protected abstract R buildModel(D data);
protected abstract R buildModel(D data) throws SourceException;

/**
* Checks, if the specific given class can be handled by this factory.
Expand All @@ -101,153 +100,4 @@ private void isSupportedClass(Class<?> desiredClass) {
.map(Class::getSimpleName)
.collect(Collectors.joining("\n - ")));
}

/**
* Returns list of sets of attribute names that the entity requires to be built. At least one of
* these sets needs to be delivered for entity creation to be successful.
*
* @param entityClass class that can be used to specify the fields that are returned
* @return list of possible attribute sets
*/
protected abstract List<Set<String>> getFields(Class<?> entityClass);

/**
* Method to find and return additional fields that were found in a source and are not used by the
* data model. This method will return the minimal unused fields among all field sets, meaning
* that the set of actual fields is compared to the field set with the least unused fields.
*
* @param actualFields found in the source
* @param validFieldSets that contains at least all fields found in the source
* @return a set of unused fields
*/
protected Set<String> getUnusedFields(
Set<String> actualFields, List<Set<String>> validFieldSets) {
// checking for additional fields
// and returning the set with the least additional fields
return validFieldSets.stream()
.map(
s -> {
Set<String> set = new HashSet<>(actualFields);
set.removeAll(s);
return set;
})
.min(Comparator.comparing(Collection::size))
.orElse(Collections.emptySet());
}

/**
* Method for validating the actual fields. The actual fields need to fully contain at least one
* of the sets returned by {@link #getFields(Class)}. If the actual fields don't contain all
* necessary fields, an {@link FactoryException} with a detail message is thrown. If the actual
* fields contain more fields than necessary, these fields are ignored.
*
* @param actualFields that were found
* @param entityClass of the build data
* @return either an exception wrapped by a {@link Failure} or an empty success
*/
public Try<Void, ValidationException> validate(
Set<String> actualFields, Class<? extends C> entityClass) {
List<Set<String>> fieldSets = getFields(entityClass);
Set<String> harmonizedActualFields = toCamelCase(actualFields);

// comparing the actual fields to a list of possible fields (allows additional fields)
// if not all fields were found in a set, this set is filtered out
// all other fields are saved as a list
// allows snake, camel and mixed cases
List<Set<String>> validFieldSets =
fieldSets.stream().filter(harmonizedActualFields::containsAll).toList();

if (validFieldSets.isEmpty()) {
// build the exception string with extensive debug information
String providedKeysString = "[" + String.join(", ", actualFields) + "]";

String possibleOptions = getFieldsString(fieldSets).toString();

return Failure.of(
new FailedValidationException(
"The provided fields "
+ providedKeysString
+ " are invalid for instance of '"
+ entityClass.getSimpleName()
+ "'. \nThe following fields (without complex objects e.g. nodes, operators, ...) to be passed to a constructor of '"
+ entityClass.getSimpleName()
+ "' are possible (NOT case-sensitive!):\n"
+ possibleOptions));
} else {
// find all unused fields
Set<String> unused = getUnusedFields(harmonizedActualFields, validFieldSets);

if (!unused.isEmpty()) {
log.info(
"The following additional fields were found for entity class of '{}': {}",
entityClass.getSimpleName(),
unused);
}

return Success.empty();
}
}

protected static StringBuilder getFieldsString(List<Set<String>> fieldSets) {
StringBuilder possibleOptions = new StringBuilder();
for (int i = 0; i < fieldSets.size(); i++) {
Set<String> fieldSet = fieldSets.get(i);
String option =
i
+ ": ["
+ String.join(", ", fieldSet)
+ "] or ["
+ String.join(", ", toSnakeCase(fieldSet))
+ "]\n";
possibleOptions.append(option);
}
return possibleOptions;
}

/**
* Creates a new set of attribute names from given list of attributes. This method should always
* be used when returning attribute sets, i.e. through {@link #getFields(Class)}.
*
* @param attributes attribute names
* @return new set exactly containing attribute names
*/
protected static TreeSet<String> newSet(String... attributes) {
TreeSet<String> set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
set.addAll(Arrays.asList(attributes));
return set;
}

/**
* Expands a set of attributes with further attributes. This method should always be used when
* returning attribute sets, i.e. through getting the needed fields. The set maintains a
* lexicographic order, that is case-insensitive.
*
* @param attributeSet set of attributes to expand
* @param more attribute names to expand given set with
* @return new set exactly containing given attribute set plus additional attributes
*/
protected static TreeSet<String> expandSet(Set<String> attributeSet, String... more) {
TreeSet<String> newSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newSet.addAll(attributeSet);
newSet.addAll(Arrays.asList(more));
return newSet;
}

protected static Set<String> toSnakeCase(Set<String> set) {
TreeSet<String> newSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newSet.addAll(set.stream().map(StringUtils::camelCaseToSnakeCase).toList());
return newSet;
}

protected static Set<String> toCamelCase(Set<String> set) {
TreeSet<String> newSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newSet.addAll(set.stream().map(StringUtils::snakeCaseToCamelCase).toList());
return newSet;
}

protected static Set<String> toLowerCase(Set<String> set) {
TreeSet<String> newSet = new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
newSet.addAll(set.stream().map(String::toLowerCase).toList());
return newSet;
}
}
20 changes: 11 additions & 9 deletions src/main/java/edu/ie3/datamodel/io/factory/FactoryData.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
package edu.ie3.datamodel.io.factory;

import edu.ie3.datamodel.exceptions.FactoryException;
import edu.ie3.datamodel.exceptions.SourceException;
import java.util.*;
import javax.measure.Quantity;
import javax.measure.Unit;
Expand Down Expand Up @@ -48,9 +49,9 @@ public boolean containsKey(String key) {
* @param field field name
* @return field value
*/
public String getField(String field) {
public String getField(String field) throws SourceException {
if (!fieldsToAttributes.containsKey(field))
throw new FactoryException(String.format("Field \"%s\" not found in EntityData", field));
throw new SourceException(String.format("Field \"%s\" not found in EntityData", field));

return fieldsToAttributes.get(field);
}
Expand All @@ -74,7 +75,8 @@ public Optional<String> getFieldOptional(String field) {
* @param <Q> unit type parameter
* @return Quantity of given field with given unit
*/
public <Q extends Quantity<Q>> ComparableQuantity<Q> getQuantity(String field, Unit<Q> unit) {
public <Q extends Quantity<Q>> ComparableQuantity<Q> getQuantity(String field, Unit<Q> unit)
throws SourceException {
return Quantities.getQuantity(getDouble(field), unit);
}

Expand All @@ -101,11 +103,11 @@ public <Q extends Quantity<Q>> Optional<ComparableQuantity<Q>> getQuantityOption
* @param field field name
* @return int value
*/
public int getInt(String field) {
public int getInt(String field) throws SourceException {
try {
return Integer.parseInt(getField(field));
} catch (NumberFormatException nfe) {
throw new FactoryException(
throw new SourceException(
String.format(
"Exception while trying to parse field \"%s\" with supposed int value \"%s\"",
field, getField(field)),
Expand All @@ -120,11 +122,11 @@ field, getField(field)),
* @param field field name
* @return double value
*/
public double getDouble(String field) {
public double getDouble(String field) throws SourceException {
try {
return Double.parseDouble(getField(field));
} catch (NumberFormatException nfe) {
throw new FactoryException(
throw new SourceException(
String.format(
"Exception while trying to parse field \"%s\" with supposed double value \"%s\"",
field, getField(field)),
Expand All @@ -139,11 +141,11 @@ field, getField(field)),
* @param field field name
* @return UUID
*/
public UUID getUUID(String field) {
public UUID getUUID(String field) throws SourceException {
try {
return UUID.fromString(getField(field));
} catch (IllegalArgumentException iae) {
throw new FactoryException(
throw new SourceException(
String.format(
"Exception while trying to parse UUID of field \"%s\" with value \"%s\"",
field, getField(field)),
Expand Down
Loading
Loading