Skip to content

Commit e9f21ae

Browse files
committed
Improved error messages when reading and validating an invalid grid
1 parent 1425d19 commit e9f21ae

File tree

7 files changed

+123
-71
lines changed

7 files changed

+123
-71
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1313
### Changed
1414
- Updated dependabot workflow and added CODEOWNERS [#1328](https://github.com/ie3-institute/PowerSystemDataModel/issues/1328)
1515
- Extend azimuth angle range to [-180°, 180°] for PV inputs [#1330](https://github.com/ie3-institute/PowerSystemDataModel/issues/1330)
16+
- Improved error messages when reading and validating an invalid grid [#1354](https://github.com/ie3-institute/PowerSystemDataModel/issues/1354)
1617

1718
## [7.0.0] - 2025-05-08
1819

src/main/java/edu/ie3/datamodel/io/source/csv/CsvDataSource.java

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -79,8 +79,19 @@ public Optional<Set<String>> getSourceFields(Class<? extends Entity> entityClass
7979
*/
8080
public Optional<Set<String>> getSourceFields(Path filePath) throws SourceException {
8181
try (BufferedReader reader = connector.initReader(filePath)) {
82-
return Optional.of(
83-
Arrays.stream(parseCsvRow(reader.readLine(), csvSep)).collect(Collectors.toSet()));
82+
String line = reader.readLine();
83+
String[] headline = parseCsvRow(line, csvSep);
84+
85+
if (headline.length <= 1) {
86+
throw new SourceException(
87+
"The given file has less than two columns! (Used separator '"
88+
+ csvSep
89+
+ "' on headline '"
90+
+ line
91+
+ "')");
92+
}
93+
94+
return Optional.of(Arrays.stream(headline).collect(Collectors.toSet()));
8495
} catch (FileNotFoundException e) {
8596
// A file not existing can be acceptable in many cases, and is handled elsewhere.
8697
log.debug("The source for the given entity couldn't be found! Cause: {}", e.getMessage());
@@ -208,36 +219,40 @@ protected Set<Path> getTimeSeriesFilePaths(Pattern pattern) {
208219
*/
209220
protected Map<String, String> buildFieldsToAttributes(
210221
final String csvRow, final String[] headline) throws SourceException {
211-
212-
TreeMap<String, String> insensitiveFieldsToAttributes =
213-
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
214-
222+
// parse row
215223
String[] fieldVals = parseCsvRow(csvRow, csvSep);
216-
insensitiveFieldsToAttributes.putAll(
217-
IntStream.range(0, Math.min(fieldVals.length, headline.length))
218-
.boxed()
219-
.collect(
220-
Collectors.toMap(
221-
k -> StringUtils.snakeCaseToCamelCase(headline[k]), v -> fieldVals[v])));
222224

225+
// check if the number row elements matched the number of headline elements
223226
if (fieldVals.length != headline.length) {
227+
String headlineElements = "['" + String.join("', '", headline) + "']";
228+
String parsedRow = "['" + String.join("', '", fieldVals) + "']";
229+
224230
throw new SourceException(
225231
"The size of the headline ("
226232
+ headline.length
227233
+ ") does not fit to the size of the attribute fields ("
228234
+ fieldVals.length
229235
+ ").\nHeadline: "
230-
+ String.join(", ", headline)
231-
+ "\nRow: "
232-
+ csvRow.trim()
236+
+ headlineElements
237+
+ "\nParsed row: "
238+
+ parsedRow
233239
+ ".\nPlease check:"
234-
+ "\n - is the csv separator in the file matching the separator provided in the constructor ('"
240+
+ "\n - is the csv separator in the row matching the provided separator '"
235241
+ csvSep
236-
+ "')"
242+
+ "'"
237243
+ "\n - does the number of columns match the number of headline fields "
238244
+ "\n - are you using a valid RFC 4180 formatted csv row?");
239245
}
240246

247+
TreeMap<String, String> insensitiveFieldsToAttributes =
248+
new TreeMap<>(String.CASE_INSENSITIVE_ORDER);
249+
insensitiveFieldsToAttributes.putAll(
250+
IntStream.range(0, Math.min(fieldVals.length, headline.length))
251+
.boxed()
252+
.collect(
253+
Collectors.toMap(
254+
k -> StringUtils.snakeCaseToCamelCase(headline[k]), v -> fieldVals[v])));
255+
241256
if (insensitiveFieldsToAttributes.size() != fieldVals.length) {
242257
throw new SourceException(
243258
"There might be duplicate headline elements.\nHeadline: "
@@ -298,7 +313,9 @@ protected Try<Stream<Map<String, String>>, SourceException> buildStreamWithField
298313
// is wanted to avoid a lock on the file), but this causes a closing of the stream as well.
299314
// As we still want to consume the data at other places, we start a new stream instead of
300315
// returning the original one
301-
return csvRowFieldValueMapping(reader, headline);
316+
return csvRowFieldValueMapping(reader, headline, filePath.getFileName())
317+
.transformF(
318+
e -> new SourceException("The file '" + filePath + "' could not be parsed.", e));
302319
} catch (FileNotFoundException e) {
303320
if (allowFileNotExisting) {
304321
log.warn("Unable to find file '{}': {}", filePath, e.getMessage());
@@ -324,10 +341,11 @@ private Try<Path, SourceException> getFilePath(Class<? extends Entity> entityCla
324341
*
325342
* @param reader for the file
326343
* @param headline of the file
344+
* @param fileName the name of the file, that is read
327345
* @return a list of mapping
328346
*/
329347
protected Try<Stream<Map<String, String>>, SourceException> csvRowFieldValueMapping(
330-
BufferedReader reader, String[] headline) {
348+
BufferedReader reader, String[] headline, Path fileName) {
331349
return Try.scanStream(
332350
reader
333351
.lines()
@@ -337,7 +355,7 @@ protected Try<Stream<Map<String, String>>, SourceException> csvRowFieldValueMapp
337355
Try.of(
338356
() -> buildFieldsToAttributes(csvRow, headline),
339357
SourceException.class)),
340-
"Map<String, String>")
358+
fileName.toString())
341359
.transform(
342360
stream -> stream.filter(map -> !map.isEmpty()),
343361
e -> new SourceException("Parsing csv row failed.", e));

src/main/java/edu/ie3/datamodel/io/source/csv/CsvIdCoordinateSource.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -209,7 +209,7 @@ private Collection<Point> getCoordinatesInBoundingBox(
209209
// is wanted to avoid a lock on the file), but this causes a closing of the stream as well.
210210
// As we still want to consume the data at other places, we start a new stream instead of
211211
// returning the original one
212-
return dataSource.csvRowFieldValueMapping(reader, headline);
212+
return dataSource.csvRowFieldValueMapping(reader, headline, filePath.getFileName());
213213
} catch (IOException e) {
214214
return Failure.of(
215215
new SourceException("Cannot read the file for coordinate id to coordinate mapping.", e));

src/main/java/edu/ie3/datamodel/io/source/csv/CsvWeatherSource.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -177,9 +177,11 @@ private Map<Point, IndividualTimeSeries<WeatherValue>> readWeatherTimeSeries(
177177
this::buildWeatherValue;
178178
/* Reading in weather time series */
179179
for (CsvIndividualTimeSeriesMetaInformation data : weatherMetaInformation) {
180+
Path path = data.getFullFilePath();
181+
180182
// we need a reader for each file
181-
try (BufferedReader reader = connector.initReader(data.getFullFilePath())) {
182-
buildStreamWithFieldsToAttributesMap(reader)
183+
try (BufferedReader reader = connector.initReader(path)) {
184+
buildStreamWithFieldsToAttributesMap(reader, path.getFileName())
183185
.getOrThrow()
184186
.map(fieldToValueFunction)
185187
.flatMap(Optional::stream)
@@ -227,7 +229,7 @@ private Map<Point, IndividualTimeSeries<WeatherValue>> readWeatherTimeSeries(
227229
}
228230

229231
private Try<Stream<Map<String, String>>, SourceException> buildStreamWithFieldsToAttributesMap(
230-
BufferedReader bufferedReader) throws ValidationException {
232+
BufferedReader bufferedReader, Path fileName) throws ValidationException {
231233
Class<? extends Entity> entityClass = TimeBasedValue.class;
232234

233235
try (BufferedReader reader = bufferedReader) {
@@ -240,7 +242,7 @@ private Try<Stream<Map<String, String>>, SourceException> buildStreamWithFieldsT
240242
// is wanted to avoid a lock on the file), but this causes a closing of the stream as well.
241243
// As we still want to consume the data at other places, we start a new stream instead of
242244
// returning the original one
243-
return dataSource.csvRowFieldValueMapping(reader, headline);
245+
return dataSource.csvRowFieldValueMapping(reader, headline, fileName);
244246
} catch (IOException e) {
245247
return Failure.of(
246248
new SourceException(

src/main/java/edu/ie3/datamodel/utils/Try.java

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,20 @@ public static <E extends Exception> Try<Void, E> ofVoid(
6767
}
6868
}
6969

70+
/**
71+
* Method to create multiple {@link Try} object easily.
72+
*
73+
* @param suppliers that either return no data or throw an exception
74+
* @param clazz class of the exception
75+
* @return a collection of try objects
76+
* @param <E> type of exception that could be thrown
77+
*/
78+
@SafeVarargs
79+
public static <E extends Exception> Collection<Try<Void, E>> ofVoids(
80+
Class<E> clazz, VoidSupplier<E>... suppliers) {
81+
return Arrays.stream(suppliers).map(supplier -> ofVoid(supplier, clazz)).toList();
82+
}
83+
7084
/**
7185
* Method to create a {@link Try} object easily.
7286
*

src/main/java/edu/ie3/datamodel/utils/validation/GridContainerValidationUtils.java

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -99,10 +99,15 @@ private GridContainerValidationUtils() {
9999

100100
/* sanity check to ensure uniqueness */
101101
List<Try<Void, ? extends ValidationException>> exceptions = new ArrayList<>();
102-
exceptions.add(
103-
Try.ofVoid(
104-
() -> checkAssetUniqueness(rawGridElements.allEntitiesAsList()),
105-
DuplicateEntitiesException.class));
102+
exceptions.addAll(
103+
Try.ofVoids(
104+
DuplicateEntitiesException.class,
105+
() -> checkAssetUniqueness(rawGridElements.getNodes()),
106+
() -> checkAssetUniqueness(rawGridElements.getLines()),
107+
() -> checkAssetUniqueness(rawGridElements.getSwitches()),
108+
() -> checkAssetUniqueness(rawGridElements.getTransformer2Ws()),
109+
() -> checkAssetUniqueness(rawGridElements.getTransformer3Ws()),
110+
() -> checkAssetUniqueness(rawGridElements.getMeasurementUnits())));
106111

107112
/* Checking nodes */
108113
Set<NodeInput> nodes = rawGridElements.getNodes();
@@ -287,10 +292,6 @@ protected static Try<Void, InvalidGridException> checkConnectivity(
287292

288293
/* sanity check to ensure uniqueness */
289294
List<Try<Void, ? extends ValidationException>> exceptions = new ArrayList<>();
290-
exceptions.add(
291-
Try.ofVoid(
292-
() -> checkAssetUniqueness(systemParticipants.allEntitiesAsList()),
293-
DuplicateEntitiesException.class));
294295

295296
exceptions.addAll(checkSystemParticipants(systemParticipants.getBmPlants(), nodes));
296297
exceptions.addAll(checkSystemParticipants(systemParticipants.getChpPlants(), nodes));
@@ -306,8 +307,8 @@ protected static Try<Void, InvalidGridException> checkConnectivity(
306307
}
307308

308309
/**
309-
* Checks the validity of specific system participant. Moreover, it checks, if the systems are
310-
* connected to a node that is not in the provided set
310+
* Checks the validity and uniqueness of specific system participant. Moreover, it checks, if the
311+
* systems are connected to a node that is not in the provided set
311312
*
312313
* @param participants a set of specific system participants
313314
* @param nodes Set of already known nodes
@@ -316,18 +317,17 @@ protected static Try<Void, InvalidGridException> checkConnectivity(
316317
*/
317318
protected static List<Try<Void, ? extends ValidationException>> checkSystemParticipants(
318319
Set<? extends SystemParticipantInput> participants, Set<NodeInput> nodes) {
319-
return participants.stream()
320-
.map(
321-
entity -> {
322-
List<Try<Void, ? extends ValidationException>> exceptions = new ArrayList<>();
323-
324-
exceptions.add(checkNodeAvailability(entity, nodes));
325-
exceptions.addAll(SystemParticipantValidationUtils.check(entity));
326-
327-
return exceptions;
328-
})
329-
.flatMap(List::stream)
330-
.toList();
320+
List<Try<Void, ? extends ValidationException>> exceptions = new ArrayList<>();
321+
exceptions.add(
322+
Try.ofVoid(() -> checkAssetUniqueness(participants), DuplicateEntitiesException.class));
323+
324+
participants.forEach(
325+
participant -> {
326+
exceptions.add(checkNodeAvailability(participant, nodes));
327+
exceptions.addAll(SystemParticipantValidationUtils.check(participant));
328+
});
329+
330+
return exceptions;
331331
}
332332

333333
/**

src/main/java/edu/ie3/datamodel/utils/validation/UniquenessValidationUtils.java

Lines changed: 41 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,11 @@
2424
public class UniquenessValidationUtils extends ValidationUtils {
2525

2626
// default field set supplier
27-
protected static final FieldSetSupplier<UniqueEntity> uuidFieldSupplier =
27+
protected static final FieldSetSupplier<? extends UniqueEntity> uuidFieldSupplier =
2828
entity -> Set.of(entity.getUuid());
29-
protected static final FieldSetSupplier<AssetInput> idFieldSupplier = e -> Set.of(e.getId());
30-
protected static final FieldSetSupplier<ResultEntity> resultFieldSupplier =
29+
protected static final FieldSetSupplier<? extends AssetInput> idFieldSupplier =
30+
e -> Set.of(e.getId());
31+
protected static final FieldSetSupplier<? extends ResultEntity> resultFieldSupplier =
3132
entity -> Set.of(entity.getTime(), entity.getInputModel());
3233
protected static final FieldSetSupplier<MappingEntry> mappingFieldSupplier =
3334
entity -> Set.of(entity.getAsset());
@@ -44,9 +45,10 @@ public class UniquenessValidationUtils extends ValidationUtils {
4445
* @param entities to be checked
4546
* @throws DuplicateEntitiesException if uniqueness is violated
4647
*/
47-
public static void checkUniqueEntities(Collection<? extends UniqueEntity> entities)
48+
@SuppressWarnings("unchecked")
49+
public static <E extends UniqueEntity> void checkUniqueEntities(Collection<E> entities)
4850
throws DuplicateEntitiesException {
49-
checkUniqueness(entities, uuidFieldSupplier).getOrThrow();
51+
checkUniqueness(entities, (FieldSetSupplier<? super E>) uuidFieldSupplier).getOrThrow();
5052
}
5153

5254
/**
@@ -55,13 +57,14 @@ public static void checkUniqueEntities(Collection<? extends UniqueEntity> entiti
5557
* @param entities to be checked
5658
* @throws DuplicateEntitiesException if uniqueness is violated
5759
*/
58-
public static void checkAssetUniqueness(Collection<? extends AssetInput> entities)
60+
@SuppressWarnings("unchecked")
61+
public static <E extends AssetInput> void checkAssetUniqueness(Collection<E> entities)
5962
throws DuplicateEntitiesException {
6063

6164
List<DuplicateEntitiesException> exceptions =
6265
Try.getExceptions(
6366
Try.ofVoid(() -> checkUniqueEntities(entities), DuplicateEntitiesException.class),
64-
checkUniqueness(entities, idFieldSupplier));
67+
checkUniqueness(entities, (FieldSetSupplier<? super E>) idFieldSupplier));
6568

6669
if (!exceptions.isEmpty()) {
6770
throw new DuplicateEntitiesException("AssetInput", exceptions);
@@ -74,9 +77,10 @@ public static void checkAssetUniqueness(Collection<? extends AssetInput> entitie
7477
* @param entities to be checked
7578
* @throws DuplicateEntitiesException if uniqueness is violated
7679
*/
77-
public static void checkResultUniqueness(Collection<? extends ResultEntity> entities)
80+
@SuppressWarnings("unchecked")
81+
public static <E extends ResultEntity> void checkResultUniqueness(Collection<E> entities)
7882
throws DuplicateEntitiesException {
79-
checkUniqueness(entities, resultFieldSupplier).getOrThrow();
83+
checkUniqueness(entities, (FieldSetSupplier<? super E>) resultFieldSupplier).getOrThrow();
8084
}
8185

8286
/**
@@ -122,33 +126,46 @@ public static void checkWeatherUniqueness(Collection<TimeBasedValue<WeatherValue
122126
*/
123127
private static <E extends Entity> Try<Void, DuplicateEntitiesException> checkUniqueness(
124128
Collection<? extends E> entities, FieldSetSupplier<E> supplier) {
129+
Optional<String> option = entities.stream().findAny().map(e -> e.getClass().getSimpleName());
130+
if (option.isPresent()) {
131+
return checkUniqueness(entities, supplier, option.get());
132+
} else {
133+
return Try.Success.empty();
134+
}
135+
}
136+
137+
/**
138+
* Checking the uniqueness for a given {@link Entity}.
139+
*
140+
* @param entities to be checked
141+
* @param supplier for the field set
142+
* @param entityName name of the class of the entity
143+
* @return a try object
144+
* @param <E> type of entity
145+
*/
146+
private static <E extends Entity> Try<Void, DuplicateEntitiesException> checkUniqueness(
147+
Collection<? extends E> entities, FieldSetSupplier<E> supplier, String entityName) {
125148
if (entities.size() < 2) {
126149
return Success.empty();
127150
}
128151

129-
return entities.stream()
130-
.findAny()
131-
.map(
132-
entity -> {
133-
List<Set<Object>> elements = entities.stream().map(supplier::getFieldSets).toList();
134-
Set<Set<Object>> uniqueElements = new HashSet<>(elements);
135-
136-
return Try.ofVoid(
137-
elements.size() != uniqueElements.size(),
138-
() -> buildDuplicationException(entity.getClass(), elements));
139-
})
140-
.orElse(Success.empty());
152+
List<Set<Object>> elements = entities.stream().map(supplier::getFieldSets).toList();
153+
Set<Set<Object>> uniqueElements = new HashSet<>(elements);
154+
155+
return Try.ofVoid(
156+
elements.size() != uniqueElements.size(),
157+
() -> buildDuplicationException(entityName, elements));
141158
}
142159

143160
/**
144161
* Method for building a {@link DuplicateEntitiesException}.
145162
*
146-
* @param entityClass class of the entity
163+
* @param entityClass name of the class of the entity
147164
* @param notUniqueElements list of not unique elements
148165
* @return a {@link DuplicateEntitiesException}
149166
*/
150167
protected static DuplicateEntitiesException buildDuplicationException(
151-
Class<? extends Entity> entityClass, List<Set<Object>> notUniqueElements) {
168+
String entityClass, List<Set<Object>> notUniqueElements) {
152169
String fieldName =
153170
notUniqueElements.get(0).stream()
154171
.map(f -> f.getClass().getSimpleName())
@@ -167,7 +184,7 @@ protected static DuplicateEntitiesException buildDuplicationException(
167184

168185
return new DuplicateEntitiesException(
169186
"'"
170-
+ entityClass.getSimpleName()
187+
+ entityClass
171188
+ "' entities with duplicated "
172189
+ fieldName
173190
+ " key, but different field "

0 commit comments

Comments
 (0)