XMLImporterTest.java

/**
 * Copyright (c) 2016, 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.iidm.serde;

import com.google.common.io.ByteStreams;
import com.powsybl.commons.datasource.*;
import com.powsybl.commons.report.PowsyblCoreReportResourceBundle;
import com.powsybl.commons.test.PowsyblCoreTestReportResourceBundle;
import com.powsybl.commons.report.ReportNode;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.NetworkFactory;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.Arrays;
import java.util.List;
import java.util.Properties;

import static com.powsybl.commons.test.TestUtil.normalizeLineSeparator;
import static com.powsybl.iidm.serde.IidmSerDeConstants.CURRENT_IIDM_VERSION;
import static org.junit.jupiter.api.Assertions.*;

/**
 * @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
 */
class XMLImporterTest extends AbstractIidmSerDeTest {

    private XMLImporter importer;

    private void writeNetwork(String fileName, IidmVersion version, boolean writeExt) throws IOException {
        writeNetwork(fileName, version.getNamespaceURI(), writeExt);
    }

    private void writeNetwork(String fileName, String namespaceUri, boolean writeExt) throws IOException {
        try (BufferedWriter writer = Files.newBufferedWriter(fileSystem.getPath(fileName), StandardCharsets.UTF_8)) {
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            writer.write("<iidm:network xmlns:iidm=\"" + namespaceUri + "\" id=\"test\" caseDate=\"2013-01-15T18:45:00.000+01:00\" forecastDistance=\"0\" sourceFormat=\"test\" minimumValidationLevel=\"STEADY_STATE_HYPOTHESIS\">");
            writer.newLine();
            writer.write("    <iidm:substation id=\"P1\" country=\"FR\"/>");
            writer.newLine();
            if (writeExt) {
                writer.write("    <iidm:extension id=\"P1\">");
                writer.write("    <foo/>");
                writer.write("    </iidm:extension>");
            }
            writer.write("</iidm:network>");
            writer.newLine();
        }
    }

    private void writeNetworkWithExtension(String fileName, String namespaceUri) throws IOException {
        try (BufferedWriter writer = Files.newBufferedWriter(fileSystem.getPath(fileName), StandardCharsets.UTF_8)) {
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            writer.write("<iidm:network xmlns:iidm=\"" + namespaceUri + "\" id=\"test\" caseDate=\"2013-01-15T18:45:00.000+01:00\" forecastDistance=\"0\" sourceFormat=\"test\" minimumValidationLevel=\"STEADY_STATE_HYPOTHESIS\">");
            writer.newLine();
            writer.write("    <iidm:substation id=\"P1\" country=\"FR\"/>");
            writer.newLine();
            writer.write("    <iidm:extension id=\"P1\">");
            writer.write("    <substationPosition>");
            writer.write("    <coordinate latitude=\"1\" longitude=\"2\" />");
            writer.write("    </substationPosition>");
            writer.write("    </iidm:extension>");
            writer.write("</iidm:network>");
            writer.newLine();
        }
    }

    private void writeNetworkWithComment(String fileName) throws IOException {
        try (BufferedWriter writer = Files.newBufferedWriter(fileSystem.getPath(fileName), StandardCharsets.UTF_8)) {
            writer.write("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
            writer.newLine();
            writer.write("<!--sfsfs-->");
            writer.write("<iidm:network xmlns:iidm=\"" + CURRENT_IIDM_VERSION.getNamespaceURI() + "\" id=\"test\" caseDate=\"2013-01-15T18:45:00.000+01:00\" forecastDistance=\"0\" sourceFormat=\"test\" minimumValidationLevel=\"STEADY_STATE_HYPOTHESIS\">");
            writer.newLine();
            writer.write("    <iidm:substation id=\"P1\" country=\"FR\"/>");
            writer.newLine();
            writer.write("</iidm:network>");
            writer.newLine();
        }
    }

    @BeforeEach
    public void setUp() throws IOException {
        super.setUp();
        // create test files
        //   /test0.xiidm
        //   /test1.iidm
        //   /test2.xml
        //   /test3.txt
        //   /test5.xiidm that contains unsupported extensions
        //   /test6.xiidm + /test6_mapping.csv
        //   /test7.xiidm that contains a comment after xml prolog
        writeNetwork("/test0.xiidm", CURRENT_IIDM_VERSION, false);
        writeNetwork("/test1.iidm", CURRENT_IIDM_VERSION, false);
        writeNetwork("/test2.xml", CURRENT_IIDM_VERSION, false);
        writeNetwork("/test3.txt", CURRENT_IIDM_VERSION, false);
        writeNetwork("/test5.xiidm", CURRENT_IIDM_VERSION, true);
        writeNetwork("/test6.xiidm", CURRENT_IIDM_VERSION, false);
        writeNetwork("/testDummy.xiidm", "http://wwww.dummy.foo/", false);
        try (BufferedWriter writer = Files.newBufferedWriter(fileSystem.getPath("/test6_mapping.csv"), StandardCharsets.UTF_8)) {
            writer.write("ZZ;test");
            writer.newLine();
            writer.write("X1;P1");
            writer.newLine();
        }
        writeNetworkWithComment("/test7.xiidm");
        writeNetworkWithExtension("/test8.xiidm", CURRENT_IIDM_VERSION.getNamespaceURI());

        importer = new XMLImporter();
    }

    @Test
    void backwardCompatibilityTest() throws IOException {
        // create network and datasource
        writeNetwork("/v_1_0.xiidm", IidmVersion.V_1_0, false);
        DataSource dataSource = new DirectoryDataSource(fileSystem.getPath("/"), "v_1_0");

        // exists
        assertTrue(importer.exists(dataSource));

        // importData
        Network network = importer.importData(dataSource, NetworkFactory.findDefault(), null);
        assertNotNull(network.getSubstation("P1"));
    }

    @Test
    void testMetaInfos() {
        assertEquals("XIIDM", importer.getFormat());
        assertEquals("IIDM XML v " + CURRENT_IIDM_VERSION.toString(".") + " importer", importer.getComment());
        assertEquals(List.of("xiidm", "iidm", "xml"), importer.getSupportedExtensions());
        assertEquals(5, importer.getParameters().size());
        assertEquals("iidm.import.xml.throw-exception-if-extension-not-found", importer.getParameters().get(0).getName());
        assertEquals(Arrays.asList("iidm.import.xml.throw-exception-if-extension-not-found", "throwExceptionIfExtensionNotFound"), importer.getParameters().get(0).getNames());
    }

    @Test
    void exists() {
        assertTrue(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "test0")));
        assertTrue(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "test1")));
        assertTrue(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "test2")));
        assertFalse(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "test3"))); // wrong extension
        assertFalse(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "test4"))); // does not exist
        assertFalse(importer.exists(new DirectoryDataSource(fileSystem.getPath("/"), "testDummy"))); // namespace URI is not defined
    }

    @Test
    void copy() throws Exception {
        importer.copy(new DirectoryDataSource(fileSystem.getPath("/"), "test0"), new DirectoryDataSource(fileSystem.getPath("/"), "test0_copy"));
        assertTrue(Files.exists(fileSystem.getPath("/test0_copy.xiidm")));
        assertEquals(Files.readAllLines(fileSystem.getPath("/test0.xiidm"), StandardCharsets.UTF_8),
                Files.readAllLines(fileSystem.getPath("/test0_copy.xiidm"), StandardCharsets.UTF_8));

        // test copy with id mapping file
        importer.copy(new DirectoryDataSource(fileSystem.getPath("/"), "test6"), new DirectoryDataSource(fileSystem.getPath("/"), "test6_copy"));
        assertTrue(Files.exists(fileSystem.getPath("/test6_copy.xiidm")));
        assertTrue(Files.exists(fileSystem.getPath("/test6_copy_mapping.csv")));
        assertEquals(Files.readAllLines(fileSystem.getPath("/test6.xiidm"), StandardCharsets.UTF_8),
                Files.readAllLines(fileSystem.getPath("/test6_copy.xiidm"), StandardCharsets.UTF_8));
        assertEquals(Files.readAllLines(fileSystem.getPath("/test6_mapping.csv"), StandardCharsets.UTF_8),
                Files.readAllLines(fileSystem.getPath("/test6_copy_mapping.csv"), StandardCharsets.UTF_8));
    }

    @Test
    void importData() {
        // should be ok
        assertNotNull(importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test0"), NetworkFactory.findDefault(), null));

        // should fail because file that does not exist
        try {
            importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test4"), NetworkFactory.findDefault(), null);
            fail();
        } catch (RuntimeException ignored) {
        }

        // extension plugin will be not found but default option just warn
        assertNotNull(importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test5"), NetworkFactory.findDefault(), null));

        // extension plugin will be not found but option is set to throw an exception
        // (deprecated parameter name)
        Properties params = new Properties();
        params.put("throwExceptionIfExtensionNotFound", "true");
        try {
            importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test5"), NetworkFactory.findDefault(), params);
            fail();
        } catch (RuntimeException ignored) {
        }

        // extension plugin will be not found but option is set to throw an exception
        // (parameter name following same naming convention of XmlExporter)
        Properties params2 = new Properties();
        params2.put("iidm.import.xml.throw-exception-if-extension-not-found", "true");
        try {
            importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test5"), NetworkFactory.findDefault(), params2);
            fail();
        } catch (RuntimeException ignored) {
        }

        // read file with id mapping
        Network network = importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test6"), NetworkFactory.findDefault(), params);
        assertNotNull(network.getSubstation("X1")); // and not P1 !!!!!

        Network network2 = importer.importData(new DirectoryDataSource(fileSystem.getPath("/"), "test7"), NetworkFactory.findDefault(), null);
        assertNotNull(network2.getSubstation("P1"));
    }

    @Test
    void importDataReportNodeTest() throws IOException {
        DirectoryDataSource dataSource = new DirectoryDataSource(fileSystem.getPath("/"), "test8");
        importDataAndTestReportNode("/importXmlReport.txt", dataSource);
    }

    @Test
    void importDataReportNodeExtensionNotFoundTest() throws IOException {
        DirectoryDataSource dataSource = new DirectoryDataSource(fileSystem.getPath("/"), "test5");
        importDataAndTestReportNode("/importXmlReportExtensionsNotFound.txt", dataSource);
    }

    @Test
    void importDataReportNodeMultipleExtension() throws IOException {
        importDataAndTestReportNode("multiple-extensions",
                "multiple-extensions.xml",
                "/importXmlReportExtensions.txt");
    }

    @Test
    void importDataReportNodeValidationTest() throws IOException {
        importDataAndTestReportNode("twoWindingsTransformerPhaseAndRatioTap",
                "twoWindingsTransformerPhaseAndRatioTap.xml",
                "/importXmlReportValidation.txt");
    }

    @Test
    void importDataReportNodeValidationAndMultipleExtensionTest() throws IOException {
        importDataAndTestReportNode("twoWindingsTransformerPhaseAndRatioTapWithExtensions",
                "twoWindingsTransformerPhaseAndRatioTapWithExtensions.xml",
                "/importXmlReportExtensionsAndValidations.txt");
    }

    private void importDataAndTestReportNode(String dataSourceBaseName, String dataSourceFilename, String expectedContentFilename) throws IOException {
        ReadOnlyDataSource dataSource = new ResourceDataSource(dataSourceBaseName, new ResourceSet(getVersionDir(CURRENT_IIDM_VERSION), dataSourceFilename));
        importDataAndTestReportNode(expectedContentFilename, dataSource);
    }

    private void importDataAndTestReportNode(String expectedContentFilename, ReadOnlyDataSource dataSource) throws IOException {
        ReportNode reportNode = ReportNode.newRootReportNode()
                .withResourceBundles(PowsyblCoreTestReportResourceBundle.TEST_BASE_NAME, PowsyblCoreReportResourceBundle.BASE_NAME)
                .withMessageTemplate("test")
                .build();
        assertNotNull(importer.importData(dataSource, NetworkFactory.findDefault(), null, reportNode));

        StringWriter sw = new StringWriter();
        reportNode.print(sw);
        InputStream ref = XMLImporterTest.class.getResourceAsStream(expectedContentFilename);
        String refLogExport = normalizeLineSeparator(new String(ByteStreams.toByteArray(ref), StandardCharsets.UTF_8));
        String logExport = normalizeLineSeparator(sw.toString());
        assertEquals(refLogExport, logExport);
    }

}