Skip to content
Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -20,17 +20,23 @@
import java.io.UTFDataFormatException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;

import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;

import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import jakarta.xml.bind.ValidationEvent;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.XMLStreamReader;
import jakarta.xml.bind.ValidationEventLocator;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand Down Expand Up @@ -167,13 +173,56 @@ private static AeriusException newGmlParseError(final XMLStreamException e) {
}

private static List<AeriusException> newValidationFailed(final List<ValidationEvent> list) {
final List<AeriusException> errors = new ArrayList<>();
final Map<String, List<ValidationEvent>> grouped = new LinkedHashMap<>();
final Map<String, String> prefixByKey = new HashMap<>();
for (final ValidationEvent event : list) {
final String errorMessage = event.getMessage().trim();
final AeriusException error = new AeriusException(ImaerExceptionReason.GML_VALIDATION_FAILED, errorMessage);
errors.add(error);
LOG.debug("validation error: {}", errorMessage);
final String key = locationKey(event.getLocator());
grouped.computeIfAbsent(key, k -> new ArrayList<>()).add(event);
prefixByKey.computeIfAbsent(key, k -> locationPrefix(event.getLocator()));
}

final List<AeriusException> errors = new ArrayList<>();
for (final Map.Entry<String, List<ValidationEvent>> entry : grouped.entrySet()) {
final String body = discardRedundantLowerSeverityEvents(entry.getValue()).stream()
.map(e -> e.getMessage().trim())
.collect(Collectors.joining(" "));
final String message = prefixByKey.get(entry.getKey()) + body;
errors.add(new AeriusException(ImaerExceptionReason.GML_VALIDATION_FAILED, message));
LOG.debug("validation error: {}", message);
}
return errors;
}

/** Lossy: drops events below the group's max severity (e.g. JAXB's bare "None" when a cvc-* FATAL covers it). */
private static List<ValidationEvent> discardRedundantLowerSeverityEvents(final List<ValidationEvent> sameLocation) {
final int maxSeverity = sameLocation.stream().mapToInt(ValidationEvent::getSeverity).max().orElse(0);
return sameLocation.stream()
.filter(e -> e.getSeverity() == maxSeverity)
.toList();
}

private static String locationKey(final ValidationEventLocator locator) {
if (locator == null) {
return "-1:-1";
}
return locator.getLineNumber() + ":" + locator.getColumnNumber();
}

private static String locationPrefix(final ValidationEventLocator locator) {
if (locator == null) {
return "";
}
final int line = locator.getLineNumber();
final int col = locator.getColumnNumber();
if (line >= 0 && col >= 0) {
return "[line %d, col %d] ".formatted(line, col);
}
if (line >= 0) {
return "[line %d] ".formatted(line);
}
if (col >= 0) {
return "[col %d] ".formatted(col);
}
return "";
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -93,13 +93,16 @@ void testGMLGeometryNotPermitted() throws IOException {
@Test
void testGMLMultipleErrors() throws IOException, AeriusException {
final List<String> expectedErrors = List.of(
"None",
"cvc-datatype-valid.1.2.1: 'None' is not a valid value for 'double'.",
"cvc-type.3.1.3: The value 'None' of element 'imaer:vehiclesPerTimeUnit' is not valid.",
"cvc-enumeration-valid: Value 'None' is not facet-valid with respect to enumeration '[HOUR, DAY, MONTH, YEAR]'. It must be a value from the enumeration.",
"cvc-type.3.1.3: The value 'None' of element 'imaer:timeUnit' is not valid.",
"cvc-complex-type.2.4.b: The content of element 'imaer:CustomVehicle' is not complete. One of '{\"http://imaer.aerius.nl/5.1\":emission}' is expected.",
"cvc-complex-type.2.4.a: Invalid content was found starting with element '{\"http://imaer.aerius.nl/5.1\":diurnalVariation}'. One of '{\"http://imaer.aerius.nl/5.1\":vehicles, \"http://imaer.aerius.nl/5.1\":roadManager, \"http://imaer.aerius.nl/5.1\":trafficDirection, \"http://imaer.aerius.nl/5.1\":width}' is expected.");
"[line 33, col 80] cvc-datatype-valid.1.2.1: 'None' is not a valid value for 'double'."
+ " cvc-type.3.1.3: The value 'None' of element 'imaer:vehiclesPerTimeUnit' is not valid.",
"[line 34, col 58] cvc-enumeration-valid: Value 'None' is not facet-valid with respect to enumeration '[HOUR, DAY, MONTH, YEAR]'."
+ " It must be a value from the enumeration."
+ " cvc-type.3.1.3: The value 'None' of element 'imaer:timeUnit' is not valid.",
"[line 36, col 39] cvc-complex-type.2.4.b: The content of element 'imaer:CustomVehicle' is not complete."
+ " One of '{\"http://imaer.aerius.nl/5.1\":emission}' is expected.",
"[line 38, col 37] cvc-complex-type.2.4.a: Invalid content was found starting with element '{\"http://imaer.aerius.nl/5.1\":diurnalVariation}'."
+ " One of '{\"http://imaer.aerius.nl/5.1\":vehicles, \"http://imaer.aerius.nl/5.1\":roadManager,"
+ " \"http://imaer.aerius.nl/5.1\":trafficDirection, \"http://imaer.aerius.nl/5.1\":width}' is expected.");
assertResults("fout_multiple_errors", expectedErrors, ImaerExceptionReason.GML_VALIDATION_FAILED);
}

Expand Down
Loading