diff --git a/commons/src/main/java/com/powsybl/dynawo/commons/timeline/CsvTimeLineParser.java b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/CsvTimeLineParser.java new file mode 100644 index 000000000..a39f2e259 --- /dev/null +++ b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/CsvTimeLineParser.java @@ -0,0 +1,81 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +import com.powsybl.commons.PowsyblException; +import com.univocity.parsers.common.ParsingContext; +import com.univocity.parsers.common.ResultIterator; +import com.univocity.parsers.csv.CsvParser; +import com.univocity.parsers.csv.CsvParserSettings; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author Laurent Issertial + */ +public final class CsvTimeLineParser implements TimeLineParser { + + private static final int NB_COLUMNS = 3; + private final char separator; + + public CsvTimeLineParser() { + this('|'); + } + + public CsvTimeLineParser(char separator) { + this.separator = separator; + } + + public List parse(Path file) { + return parse(file, separator); + } + + public static List parse(Path file, char separator) { + try (BufferedReader reader = Files.newBufferedReader(file, StandardCharsets.UTF_8)) { + return parse(reader, separator); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + static List parse(BufferedReader reader, char separator) { + Objects.requireNonNull(reader); + CsvParserSettings settings = new CsvParserSettings(); + settings.getFormat().setDelimiter(separator); + settings.getFormat().setQuoteEscape('"'); + settings.getFormat().setLineSeparator(System.lineSeparator()); + settings.setMaxColumns(NB_COLUMNS); + CsvParser csvParser = new CsvParser(settings); + ResultIterator iterator = csvParser.iterate(reader).iterator(); + return read(iterator); + } + + static List read(ResultIterator iterator) { + List timeline = new ArrayList<>(); + int iLine = 0; + while (iterator.hasNext()) { + iLine++; + String[] tokens = iterator.next(); + if (tokens.length != NB_COLUMNS) { + throw new PowsyblException("Columns of line " + iLine + " are inconsistent"); + } + TimeLineUtil.createEvent(tokens[0], tokens[1], tokens[2]) + .ifPresent(timeline::add); + } + return timeline; + } + +} diff --git a/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimeLineParser.java b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimeLineParser.java new file mode 100644 index 000000000..12635e7ae --- /dev/null +++ b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimeLineParser.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +import java.nio.file.Path; +import java.util.List; + +/** + * @author Florian Dupuy + */ +public interface TimeLineParser { + List parse(Path timeLineFile); +} diff --git a/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimeLineUtil.java b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimeLineUtil.java new file mode 100644 index 000000000..4eb9621bf --- /dev/null +++ b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimeLineUtil.java @@ -0,0 +1,38 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Optional; + +/** + * @author Florian Dupuy + */ +public final class TimeLineUtil { + + private TimeLineUtil() { + } + + private static final Logger LOGGER = LoggerFactory.getLogger(TimeLineUtil.class); + + static Optional createEvent(String time, String modelName, String message) { + if (time == null || modelName == null || message == null) { + LOGGER.warn("Inconsistent event entry (time: '{}', modelName: '{}', message: '{}')", time, modelName, message); + } else { + try { + double timeD = Double.parseDouble(time); + return Optional.of(new TimelineEntry(timeD, modelName, message)); + } catch (NumberFormatException e) { + LOGGER.warn("Inconsistent time entry '{}'", time); + } + } + return Optional.empty(); + } +} diff --git a/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimelineEntry.java b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimelineEntry.java new file mode 100644 index 000000000..c9550dbea --- /dev/null +++ b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/TimelineEntry.java @@ -0,0 +1,36 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +/** + * @author Florian Dupuy + */ +public class TimelineEntry { + + private final double time; + private final String modelName; + private final String message; + + public TimelineEntry(double time, String modelName, String message) { + this.time = time; + this.modelName = modelName; + this.message = message; + } + + public double getTime() { + return time; + } + + public String getModelName() { + return modelName; + } + + public String getMessage() { + return message; + } +} diff --git a/commons/src/main/java/com/powsybl/dynawo/commons/timeline/XmlTimeLineParser.java b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/XmlTimeLineParser.java new file mode 100644 index 000000000..4aa78e899 --- /dev/null +++ b/commons/src/main/java/com/powsybl/dynawo/commons/timeline/XmlTimeLineParser.java @@ -0,0 +1,85 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +import com.powsybl.commons.exceptions.UncheckedXmlStreamException; +import com.powsybl.commons.xml.XmlUtil; + +import javax.xml.XMLConstants; +import javax.xml.stream.XMLInputFactory; +import javax.xml.stream.XMLStreamConstants; +import javax.xml.stream.XMLStreamException; +import javax.xml.stream.XMLStreamReader; +import java.io.IOException; +import java.io.Reader; +import java.io.UncheckedIOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +/** + * @author Laurent Issertial + * @author Marcos de Miguel + */ +public final class XmlTimeLineParser implements TimeLineParser { + + private static final String TIME = "time"; + private static final String MODEL_NAME = "modelName"; + private static final String MESSAGE = "message"; + + public List parse(Path timeLineFile) { + Objects.requireNonNull(timeLineFile); + + try (Reader reader = Files.newBufferedReader(timeLineFile, StandardCharsets.UTF_8)) { + return parse(reader); + } catch (IOException e) { + throw new UncheckedIOException(e); + } catch (XMLStreamException e) { + throw new UncheckedXmlStreamException(e); + } + } + + public static List parse(Reader reader) throws XMLStreamException { + + List timeLineSeries; + XMLInputFactory factory = XMLInputFactory.newInstance(); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, ""); + factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, ""); + XMLStreamReader xmlReader = null; + try { + xmlReader = factory.createXMLStreamReader(reader); + timeLineSeries = read(xmlReader); + } finally { + if (xmlReader != null) { + xmlReader.close(); + } + } + return timeLineSeries; + } + + private static List read(XMLStreamReader xmlReader) throws XMLStreamException { + List timeline = new ArrayList<>(); + int state = xmlReader.next(); + while (state == XMLStreamConstants.COMMENT) { + state = xmlReader.next(); + } + XmlUtil.readUntilEndElement("timeline", xmlReader, () -> { + if (xmlReader.getLocalName().equals("event")) { + String time = xmlReader.getAttributeValue(null, TIME); + String modelName = xmlReader.getAttributeValue(null, MODEL_NAME); + String message = xmlReader.getAttributeValue(null, MESSAGE); + TimeLineUtil.createEvent(time, modelName, message) + .ifPresent(timeline::add); + } + }); + return timeline; + } +} diff --git a/commons/src/test/java/com/powsybl/dynawo/commons/timeline/CsvTimeLineParserTest.java b/commons/src/test/java/com/powsybl/dynawo/commons/timeline/CsvTimeLineParserTest.java new file mode 100644 index 000000000..80db74305 --- /dev/null +++ b/commons/src/test/java/com/powsybl/dynawo/commons/timeline/CsvTimeLineParserTest.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +import com.powsybl.commons.PowsyblException; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +/** + * @author Laurent Issertial + * @author Marcos de Miguel + */ +class CsvTimeLineParserTest { + + @ParameterizedTest + @ValueSource(strings = {"/timeline.log", "/timelineWithQuotes.log"}) + void testTimeline(String fileName) throws URISyntaxException { + + Path path = Path.of(Objects.requireNonNull(getClass().getResource(fileName)).toURI()); + List timeline = new CsvTimeLineParser().parse(path); + + assertEquals(5, timeline.size()); + assertEquals("PMIN : activation", timeline.get(0).getMessage()); + assertEquals("GEN____8_SM", timeline.get(0).getModelName()); + assertEquals(0., timeline.get(0).getTime(), 1e-9); + assertEquals("PMIN : activation", timeline.get(1).getMessage()); + assertEquals("GEN____3_SM", timeline.get(1).getModelName()); + assertEquals(0.0306911, timeline.get(1).getTime(), 1e-9); + assertEquals("PMIN : deactivation", timeline.get(2).getMessage()); + assertEquals("GEN____8_SM", timeline.get(2).getModelName()); + assertEquals("PMIN : deactivation", timeline.get(3).getMessage()); + assertEquals("GEN____3_SM", timeline.get(3).getModelName()); + assertEquals("PMIN : activation", timeline.get(4).getMessage()); + assertEquals("GEN____8_SM", timeline.get(4).getModelName()); + } + + @Test + void testInconsistentFile() throws URISyntaxException { + Path path = Path.of(Objects.requireNonNull(getClass().getResource("/wrongTimeline.log")).toURI()); + Exception e = assertThrows(PowsyblException.class, () -> CsvTimeLineParser.parse(path, '|')); + assertEquals("Columns of line 2 are inconsistent", e.getMessage()); + } +} diff --git a/commons/src/test/java/com/powsybl/dynawo/commons/timeline/XmlTimeLineParserTest.java b/commons/src/test/java/com/powsybl/dynawo/commons/timeline/XmlTimeLineParserTest.java new file mode 100644 index 000000000..816f9a560 --- /dev/null +++ b/commons/src/test/java/com/powsybl/dynawo/commons/timeline/XmlTimeLineParserTest.java @@ -0,0 +1,60 @@ +/** + * Copyright (c) 2023, RTE (http://www.rte-france.com/) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.dynawo.commons.timeline; + +import org.junit.jupiter.api.Test; + +import javax.xml.stream.XMLStreamException; +import java.io.InputStreamReader; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; +import java.util.Objects; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Marcos de Miguel + */ +class XmlTimeLineParserTest { + + @Test + void test() throws XMLStreamException { + + InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/timeline.xml"))); + List timeline = XmlTimeLineParser.parse(xml); + + assertEquals(5, timeline.size()); + assertEquals("PMIN : activation", timeline.get(0).getMessage()); + assertEquals("GEN____8_SM", timeline.get(0).getModelName()); + assertEquals(0., timeline.get(0).getTime(), 1e-9); + assertEquals("PMIN : activation", timeline.get(1).getMessage()); + assertEquals("GEN____3_SM", timeline.get(1).getModelName()); + assertEquals(0.030691068513160655, timeline.get(1).getTime(), 1e-9); + assertEquals("PMIN : deactivation", timeline.get(2).getMessage()); + assertEquals("GEN____8_SM", timeline.get(2).getModelName()); + assertEquals("PMIN : deactivation", timeline.get(3).getMessage()); + assertEquals("GEN____3_SM", timeline.get(3).getModelName()); + assertEquals("PMIN : activation", timeline.get(4).getMessage()); + assertEquals("GEN____8_SM", timeline.get(4).getModelName()); + } + + @Test + void parseFromPath() throws URISyntaxException { + Path path = Path.of(Objects.requireNonNull(getClass().getResource("/timeline.xml")).toURI()); + List timeline = new XmlTimeLineParser().parse(path); + assertEquals(5, timeline.size()); + } + + @Test + void testInconsistentFile() throws XMLStreamException { + InputStreamReader xml = new InputStreamReader(Objects.requireNonNull(getClass().getResourceAsStream("/wrongTimeline.xml"))); + List timeline = XmlTimeLineParser.parse(xml); + assertEquals(4, timeline.size()); + } +} diff --git a/commons/src/test/resources/timeline.log b/commons/src/test/resources/timeline.log new file mode 100644 index 000000000..2a38a405a --- /dev/null +++ b/commons/src/test/resources/timeline.log @@ -0,0 +1,5 @@ +0 | GEN____8_SM | PMIN : activation +0.0306911 | GEN____3_SM | PMIN : activation +0.348405 | GEN____8_SM | PMIN : deactivation +0.828675 | GEN____3_SM | PMIN : deactivation +0.834701 | GEN____8_SM | PMIN : activation diff --git a/commons/src/test/resources/timeline.xml b/commons/src/test/resources/timeline.xml new file mode 100644 index 000000000..b61bfb8c5 --- /dev/null +++ b/commons/src/test/resources/timeline.xml @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/commons/src/test/resources/timelineWithQuotes.log b/commons/src/test/resources/timelineWithQuotes.log new file mode 100644 index 000000000..190fcbc45 --- /dev/null +++ b/commons/src/test/resources/timelineWithQuotes.log @@ -0,0 +1,5 @@ +"0" | "GEN____8_SM" | "PMIN : activation" +"0.0306911" | "GEN____3_SM" | "PMIN : activation" +"0.348405" | "GEN____8_SM" | "PMIN : deactivation" +"0.828675" | "GEN____3_SM" | "PMIN : deactivation" +"0.834701" | "GEN____8_SM" | "PMIN : activation" diff --git a/commons/src/test/resources/wrongTimeline.log b/commons/src/test/resources/wrongTimeline.log new file mode 100644 index 000000000..2e461b5e5 --- /dev/null +++ b/commons/src/test/resources/wrongTimeline.log @@ -0,0 +1,5 @@ +0 | GEN____8_SM | PMIN : activation +0.0306911 | GEN____3_SM +0.348405 | GEN____8_SM | PMIN : deactivation +0.828675 | GEN____3_SM | PMIN : deactivation +0.834701 | GEN____8_SM | PMIN : activation diff --git a/commons/src/test/resources/wrongTimeline.xml b/commons/src/test/resources/wrongTimeline.xml new file mode 100644 index 000000000..c50244b18 --- /dev/null +++ b/commons/src/test/resources/wrongTimeline.xml @@ -0,0 +1,8 @@ + + + + + + + +