ParametersXml.java

/**
 * Copyright (c) 2020, 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/.
 */

package com.powsybl.dynawo.xml;

import com.powsybl.commons.PowsyblException;
import com.powsybl.commons.exceptions.UncheckedXmlStreamException;
import com.powsybl.commons.xml.XmlUtil;
import com.powsybl.dynawo.DynawoSimulationContext;
import com.powsybl.dynawo.DynawoSimulationParameters;
import com.powsybl.dynawo.parameters.Parameter;
import com.powsybl.dynawo.parameters.ParameterType;
import com.powsybl.dynawo.parameters.ParametersSet;
import com.powsybl.dynawo.parameters.Reference;

import javax.xml.XMLConstants;
import javax.xml.stream.*;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;

import static com.powsybl.dynawo.xml.DynawoSimulationXmlConstants.DYN_PREFIX;
import static com.powsybl.dynawo.xml.DynawoSimulationXmlConstants.DYN_URI;

/**
 * @author Marcos de Miguel {@literal <demiguelm at aia.es>}
 * @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
 */
public final class ParametersXml {

    public static final String PARAMETERS_SET_ELEMENT_NAME = "parametersSet";

    private ParametersXml() {
    }

    public static List<ParametersSet> load(InputStream parametersFile) {
        try {
            XMLStreamReader xmlReader = createXmlInputFactory().createXMLStreamReader(parametersFile);
            return readAndClose(xmlReader);
        } catch (XMLStreamException e) {
            throw new UncheckedXmlStreamException(e);
        }
    }

    public static ParametersSet load(InputStream parametersFile, String parameterSetId) {
        try {
            XMLStreamReader xmlReader = createXmlInputFactory().createXMLStreamReader(parametersFile);
            ParametersSet parametersSet = readOneSetAndClose(xmlReader, parameterSetId);
            if (parametersSet == null) {
                throw new PowsyblException("Could not find parameters set with id='" + parameterSetId + "' in given input stream");
            }
            return parametersSet;
        } catch (XMLStreamException e) {
            throw new UncheckedXmlStreamException(e);
        }
    }

    public static List<ParametersSet> load(Path parametersFile) {
        try (Reader reader = Files.newBufferedReader(parametersFile, StandardCharsets.UTF_8)) {
            XMLStreamReader xmlReader = createXmlInputFactory().createXMLStreamReader(reader);
            return readAndClose(xmlReader);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (XMLStreamException e) {
            throw new UncheckedXmlStreamException(e);
        }
    }

    public static ParametersSet load(Path parametersFile, String parameterSetId) {
        try (Reader reader = Files.newBufferedReader(parametersFile, StandardCharsets.UTF_8)) {
            XMLStreamReader xmlReader = createXmlInputFactory().createXMLStreamReader(reader);
            ParametersSet parametersSet = readOneSetAndClose(xmlReader, parameterSetId);
            if (parametersSet == null) {
                throw new PowsyblException("Could not find parameters set with id='" + parameterSetId + "' in file '" + parametersFile + "'");
            }
            return parametersSet;
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (XMLStreamException e) {
            throw new UncheckedXmlStreamException(e);
        }
    }

    private static List<ParametersSet> readAndClose(XMLStreamReader xmlReader) throws XMLStreamException {
        List<ParametersSet> parametersSets = new ArrayList<>();
        skipComments(xmlReader);
        XmlUtil.readSubElements(xmlReader, elementName -> {
            if (!elementName.equals("set")) {
                closeAndThrowException(xmlReader, xmlReader.getLocalName());
            }
            String parameterSetIdRead = xmlReader.getAttributeValue(null, "id");
            parametersSets.add(createParametersSet(xmlReader, parameterSetIdRead));
        });
        xmlReader.close();
        return parametersSets;
    }

    private static ParametersSet readOneSetAndClose(XMLStreamReader xmlReader, String parameterSetId) throws XMLStreamException {
        AtomicReference<ParametersSet> parametersSet = new AtomicReference<>();
        skipComments(xmlReader);
        XmlUtil.readSubElements(xmlReader, elementName -> {
            if (parametersSet.get() != null) {
                return;
            }
            if (!elementName.equals("set")) {
                closeAndThrowException(xmlReader, xmlReader.getLocalName());
            }
            String idSetRead = xmlReader.getAttributeValue(null, "id");
            if (idSetRead.equals(parameterSetId)) {
                parametersSet.set(createParametersSet(xmlReader, parameterSetId));
            } else {
                XmlUtil.skipSubElements(xmlReader);
            }
        });
        xmlReader.close();
        return parametersSet.get();
    }

    private static ParametersSet createParametersSet(XMLStreamReader xmlReader, String parameterSetId) {
        ParametersSet parametersSet = new ParametersSet(parameterSetId);
        XmlUtil.readSubElements(xmlReader, elementName -> {
            try {
                String name = xmlReader.getAttributeValue(null, "name");
                ParameterType type = ParameterType.valueOf(xmlReader.getAttributeValue(null, "type"));
                switch (elementName) {
                    case "par" -> {
                        String value = xmlReader.getAttributeValue(null, "value");
                        XmlUtil.readEndElementOrThrow(xmlReader);
                        parametersSet.addParameter(name, type, value);
                    }
                    case "reference" -> {
                        String origData = xmlReader.getAttributeValue(null, "origData");
                        String origName = xmlReader.getAttributeValue(null, "origName");
                        String componentId = xmlReader.getAttributeValue(null, "componentId");
                        XmlUtil.readEndElementOrThrow(xmlReader);
                        parametersSet.addReference(name, type, origData, origName, componentId);
                    }
                    default -> closeAndThrowException(xmlReader, xmlReader.getLocalName());
                }
            } catch (XMLStreamException e) {
                throw new UncheckedXmlStreamException(e);
            }

        });
        return parametersSet;
    }

    private static XMLInputFactory createXmlInputFactory() {
        XMLInputFactory factory = XMLInputFactory.newInstance();
        factory.setProperty(XMLConstants.ACCESS_EXTERNAL_DTD, "");
        factory.setProperty(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
        return factory;
    }

    private static void skipComments(XMLStreamReader xmlReader) throws XMLStreamException {
        int state = xmlReader.next();
        while (state == XMLStreamConstants.COMMENT) {
            state = xmlReader.next();
        }
    }

    private static void closeAndThrowException(XMLStreamReader xmlReader, String unexpectedElement) {
        try {
            xmlReader.close();
        } catch (XMLStreamException e) {
            throw new UncheckedXmlStreamException(e);
        }
        throw new PowsyblException("Unexpected element: " + unexpectedElement);
    }

    public static void write(Path workingDir, DynawoSimulationContext context) {
        Objects.requireNonNull(workingDir);

        write(context.getDynamicModelsParameters(), context.getSimulationParFile(), workingDir, DYN_PREFIX);
        DynawoSimulationParameters parameters = context.getDynawoSimulationParameters();
        write(parameters.getModelParameters(), DynawoSimulationParameters.MODELS_OUTPUT_PARAMETERS_FILE, workingDir, "");
        write(List.of(parameters.getNetworkParameters()), DynawoSimulationParameters.NETWORK_OUTPUT_PARAMETERS_FILE, workingDir, "");
        write(List.of(parameters.getSolverParameters()), DynawoSimulationParameters.SOLVER_OUTPUT_PARAMETERS_FILE, workingDir, "");
    }

    public static void write(Collection<ParametersSet> parametersSets, String filename, Path workingDir, String dynPrefix) {
        Path parametersPath = workingDir.resolve(filename);
        try (Writer writer = Files.newBufferedWriter(parametersPath, StandardCharsets.UTF_8)) {
            XMLStreamWriter xmlWriter = XmlStreamWriterFactory.newInstance(writer);
            try {
                xmlWriter.writeStartDocument(StandardCharsets.UTF_8.toString(), "1.0");
                xmlWriter.setPrefix(dynPrefix, DYN_URI);
                xmlWriter.writeStartElement(DYN_URI, PARAMETERS_SET_ELEMENT_NAME);
                xmlWriter.writeNamespace(dynPrefix, DYN_URI);
                for (ParametersSet parametersSet : parametersSets) {
                    writeParametersSet(xmlWriter, parametersSet);
                }
                xmlWriter.writeEndElement();
                xmlWriter.writeEndDocument();
            } finally {
                xmlWriter.close();
            }
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        } catch (XMLStreamException e) {
            throw new UncheckedXmlStreamException(e);
        }
    }

    private static void writeParametersSet(XMLStreamWriter xmlWriter, ParametersSet parametersSet) throws XMLStreamException {
        xmlWriter.writeStartElement(DYN_URI, "set");
        xmlWriter.writeAttribute("id", parametersSet.getId());
        for (Parameter par : parametersSet.getParameters().values()) {
            ParametersXml.writeParameter(xmlWriter, par.type(), par.name(), par.value());
        }
        for (Reference par : parametersSet.getReferences()) {
            ParametersXml.writeReference(xmlWriter, par.type(), par.name(), par.origData(), par.origName(), par.componentId());
        }
        xmlWriter.writeEndElement();
    }

    public static void writeParameter(XMLStreamWriter writer, ParameterType type, String name, String value) throws XMLStreamException {
        writer.writeEmptyElement(DYN_URI, "par");
        writer.writeAttribute("type", type.toString());
        writer.writeAttribute("name", name);
        writer.writeAttribute("value", value);
    }

    public static void writeReference(XMLStreamWriter writer, ParameterType type, String name, String origData, String origName, String componentId) throws XMLStreamException {
        writer.writeEmptyElement(DYN_URI, "reference");
        writer.writeAttribute("type", type.toString());
        writer.writeAttribute("name", name);
        writer.writeAttribute("origData", origData);
        writer.writeAttribute("origName", origName);
        if (componentId != null) {
            writer.writeAttribute("componentId", componentId);
        }
    }
}