eu.itesla_project.iidm.xml.NetworkXml.java Source code

Java tutorial

Introduction

Here is the source code for eu.itesla_project.iidm.xml.NetworkXml.java

Source

/**
 * 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/.
 */
package eu.itesla_project.iidm.xml;

import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import eu.itesla_project.commons.util.ServiceLoaderCache;
import eu.itesla_project.iidm.network.*;
import javanet.staxutils.IndentingXMLStreamWriter;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.stream.*;
import javax.xml.transform.Source;
import javax.xml.transform.stream.StreamSource;
import javax.xml.validation.Schema;
import javax.xml.validation.SchemaFactory;
import javax.xml.validation.Validator;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.GZIPInputStream;
import java.util.zip.GZIPOutputStream;

/**
 *
 * @author Geoffroy Jamgotchian <geoffroy.jamgotchian at rte-france.com>
 */
public class NetworkXml implements XmlConstants {

    private static final Logger LOGGER = LoggerFactory.getLogger(NetworkXml.class);

    static final String NETWORK_ROOT_ELEMENT_NAME = "network";
    private static final String EXTENSION_ELEMENT_NAME = "extension";
    private static final String IIDM_XSD = "iidm.xsd";

    // cache XMLOutputFactory to improve performance
    private static final Supplier<XMLOutputFactory> XML_OUTPUT_FACTORY_SUPPLIER = Suppliers
            .memoize(XMLOutputFactory::newFactory);

    private static final Supplier<XMLInputFactory> XML_INPUT_FACTORY_SUPPLIER = Suppliers
            .memoize(XMLInputFactory::newInstance);

    private static final Supplier<Map<String, ExtensionXml>> EXTENSIONS_SUPPLIER = Suppliers
            .memoize(() -> new ServiceLoaderCache<>(ExtensionXml.class).getServices().stream()
                    .collect(Collectors.toMap(extensionXml -> extensionXml.getExtensionName(), e -> e)));

    private static XMLStreamWriter createXmlStreamWriter(XMLExportOptions options, OutputStream os)
            throws XMLStreamException {
        XMLStreamWriter writer = XML_OUTPUT_FACTORY_SUPPLIER.get().createXMLStreamWriter(os,
                StandardCharsets.UTF_8.toString());
        if (options.isIndent()) {
            IndentingXMLStreamWriter indentingWriter = new IndentingXMLStreamWriter(writer);
            indentingWriter.setIndent(INDENT);
            writer = indentingWriter;
        }
        return writer;
    }

    private static ExtensionXml findExtensionXml(String name) {
        return EXTENSIONS_SUPPLIER.get().get(name);
    }

    private static ExtensionXml findExtensionXmlOrThrowException(String name) {
        ExtensionXml extensionXml = findExtensionXml(name);
        if (extensionXml == null) {
            throw new RuntimeException("Xml serializer not found for extension " + name);
        }
        return extensionXml;
    }

    private static Set<String> getNetworkExtensions(Network n) {
        Set<String> extensions = new TreeSet<>();
        for (Identifiable<?> identifiable : n.getIdentifiables()) {
            for (Identifiable.Extension<? extends Identifiable<?>> extension : identifiable.getExtensions()) {
                extensions.add(extension.getName());
            }
        }
        return extensions;
    }

    private static void validate(Source xml, List<Source> additionalSchemas) {
        SchemaFactory factory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
        Source[] sources = new Source[additionalSchemas.size() + 1];
        sources[0] = new StreamSource(NetworkXml.class.getResourceAsStream("/xsd/" + IIDM_XSD));
        for (int i = 0; i < additionalSchemas.size(); i++) {
            sources[i + 1] = additionalSchemas.get(i);
        }
        try {
            Schema schema = factory.newSchema(sources);
            Validator validator = schema.newValidator();
            validator.validate(xml);
        } catch (SAXException | IOException e) {
            throw new RuntimeException(e);
        }
    }

    static void validate(InputStream is) {
        validate(new StreamSource(is), Collections.emptyList());
    }

    static void validate(Path file) {
        try (InputStream is = Files.newInputStream(file)) {
            validate(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    static void validateWithExtensions(InputStream is) {
        List<Source> additionalSchemas = EXTENSIONS_SUPPLIER.get().entrySet().stream()
                .map(e -> new StreamSource(e.getValue().getXsdAsStream())).collect(Collectors.toList());
        validate(new StreamSource(is), additionalSchemas);
    }

    static void validateWithExtensions(Path file) {
        try (InputStream is = Files.newInputStream(file)) {
            validateWithExtensions(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    private static void writeExtensionNamespaces(Network n, XMLStreamWriter writer) throws XMLStreamException {
        Set<String> extensionUris = new HashSet<>();
        Set<String> extensionPrefixes = new HashSet<>();
        for (String extensionName : getNetworkExtensions(n)) {
            ExtensionXml extensionXml = findExtensionXmlOrThrowException(extensionName);
            if (extensionUris.contains(extensionXml.getNamespaceUri())) {
                throw new RuntimeException("Extension namespace URI collision");
            } else {
                extensionUris.add(extensionXml.getNamespaceUri());
            }
            if (extensionPrefixes.contains(extensionXml.getNamespacePrefix())) {
                throw new RuntimeException("Extension namespace prefix collision");
            } else {
                extensionPrefixes.add(extensionXml.getNamespacePrefix());
            }
            writer.setPrefix(extensionXml.getNamespacePrefix(), extensionXml.getNamespaceUri());
            writer.writeNamespace(extensionXml.getNamespacePrefix(), extensionXml.getNamespaceUri());
        }
    }

    private static void writeExtensions(Network n, XmlWriterContext context) throws XMLStreamException {
        for (Identifiable<?> identifiable : n.getIdentifiables()) {
            for (Identifiable.Extension<? extends Identifiable<?>> extension : identifiable.getExtensions()) {
                ExtensionXml extensionXml = findExtensionXmlOrThrowException(extension.getName());
                context.getWriter().writeStartElement(IIDM_URI, EXTENSION_ELEMENT_NAME);
                context.getWriter().writeAttribute("id",
                        context.getAnonymizer().anonymizeString(extension.getIdentifiable().getId()));
                if (extensionXml.hasSubElements()) {
                    context.getWriter().writeStartElement(extensionXml.getNamespaceUri(), extension.getName());
                } else {
                    context.getWriter().writeEmptyElement(extensionXml.getNamespaceUri(), extension.getName());
                }
                extensionXml.write(extension, context);
                if (extensionXml.hasSubElements()) {
                    context.getWriter().writeEndElement();
                }
                context.getWriter().writeEndElement();
            }
        }
    }

    public static Anonymizer write(Network n, XMLExportOptions options, OutputStream os) {
        try {
            final XMLStreamWriter writer = createXmlStreamWriter(options, os);
            writer.writeStartDocument(StandardCharsets.UTF_8.toString(), "1.0");

            writer.setPrefix(IIDM_PREFIX, IIDM_URI);
            writer.writeStartElement(IIDM_URI, NETWORK_ROOT_ELEMENT_NAME);
            writer.writeNamespace(IIDM_PREFIX, IIDM_URI);

            if (!options.isSkipExtensions()) {
                // add additional extension namespaces and check there is no collision between extensions
                writeExtensionNamespaces(n, writer);
            }

            writer.writeAttribute("id", n.getId());
            writer.writeAttribute("caseDate", n.getCaseDate().toString());
            writer.writeAttribute("forecastDistance", Integer.toString(n.getForecastDistance()));
            writer.writeAttribute("sourceFormat", n.getSourceFormat());
            BusFilter filter = BusFilter.create(n, options);
            Anonymizer anonymizer = options.isAnonymized() ? new SimpleAnonymizer() : null;
            XmlWriterContext context = new XmlWriterContext(anonymizer, writer, options, filter);
            for (Substation s : n.getSubstations()) {
                SubstationXml.INSTANCE.write(s, null, context);
            }
            for (Line l : n.getLines()) {
                if (!filter.test(l)) {
                    continue;
                }
                if (l.isTieLine()) {
                    TieLineXml.INSTANCE.write((TieLine) l, n, context);
                } else {
                    LineXml.INSTANCE.write(l, n, context);
                }
            }
            for (HvdcLine l : n.getHvdcLines()) {
                if (!filter.test(l.getConverterStation1()) && filter.test(l.getConverterStation2())) {
                    continue;
                }
                HvdcLineXml.INSTANCE.write(l, n, context);
            }

            if (!options.isSkipExtensions()) {
                // write extensions
                writeExtensions(n, context);
            }

            writer.writeEndElement();
            writer.writeEndDocument();

            return anonymizer;
        } catch (XMLStreamException e) {
            throw new RuntimeException(e);
        }
    }

    public static Anonymizer write(Network n, OutputStream os) {
        return write(n, new XMLExportOptions(), os);
    }

    public static Anonymizer write(Network n, XMLExportOptions options, Path xmlFile) {
        try (OutputStream os = Files.newOutputStream(xmlFile)) {
            return write(n, options, os);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Anonymizer write(Network n, Path xmlFile) {
        return write(n, new XMLExportOptions(), xmlFile);
    }

    public static Anonymizer writeAndValidate(Network n, Path xmlFile) {
        Anonymizer anonymizer = write(n, xmlFile);
        validateWithExtensions(xmlFile);
        return anonymizer;
    }

    public static Network read(InputStream is) {
        return read(is, new XmlImportConfig(), null);
    }

    public static Network read(InputStream is, XmlImportConfig config, Anonymizer anonymizer) {
        try {
            XMLStreamReader reader = XML_INPUT_FACTORY_SUPPLIER.get().createXMLStreamReader(is);
            reader.next();

            String id = reader.getAttributeValue(null, "id");
            DateTime date = DateTime.parse(reader.getAttributeValue(null, "caseDate"));
            int forecastDistance = XmlUtil.readOptionalIntegerAttribute(reader, "forecastDistance", 0);
            String sourceFormat = reader.getAttributeValue(null, "sourceFormat");

            Network network = NetworkFactory.create(id, sourceFormat);
            network.setCaseDate(date);
            network.setForecastDistance(forecastDistance);

            XmlReaderContext context = new XmlReaderContext(anonymizer, reader);

            Set<String> extensionNamesNotFound = new TreeSet<>();

            XmlUtil.readUntilEndElement(NETWORK_ROOT_ELEMENT_NAME, reader, () -> {
                switch (reader.getLocalName()) {
                case SubstationXml.ROOT_ELEMENT_NAME:
                    SubstationXml.INSTANCE.read(network, context);
                    break;

                case LineXml.ROOT_ELEMENT_NAME:
                    LineXml.INSTANCE.read(network, context);
                    break;

                case TieLineXml.ROOT_ELEMENT_NAME:
                    TieLineXml.INSTANCE.read(network, context);
                    break;

                case HvdcLineXml.ROOT_ELEMENT_NAME:
                    HvdcLineXml.INSTANCE.read(network, context);
                    break;

                case EXTENSION_ELEMENT_NAME:
                    String id2 = context.getAnonymizer().deanonymizeString(reader.getAttributeValue(null, "id"));
                    Identifiable identifiable = network.getIdentifiable(id2);
                    if (identifiable == null) {
                        throw new RuntimeException("Identifiable " + id2 + " not found");
                    }
                    XmlUtil.readUntilEndElement(EXTENSION_ELEMENT_NAME, reader, () -> {
                        String extensionName = reader.getLocalName();
                        ExtensionXml extensionXml = findExtensionXml(extensionName);
                        if (extensionXml != null) {
                            Identifiable.Extension<? extends Identifiable<?>> extension = extensionXml
                                    .read(identifiable, context);
                            identifiable.addExtension(extensionXml.getExtensionClass(), extension);
                        } else {
                            extensionNamesNotFound.add(extensionName);
                        }
                    });
                    break;

                default:
                    throw new AssertionError();
                }
            });

            context.getEndTasks().forEach(Runnable::run);

            if (extensionNamesNotFound.size() > 0) {
                if (config.isThrowExceptionIfExtensionNotFound()) {
                    throw new RuntimeException("Extensions " + extensionNamesNotFound + " not found");
                } else {
                    LOGGER.error("Extensions {} not found", extensionNamesNotFound);
                }
            }

            return network;
        } catch (XMLStreamException e) {
            throw new RuntimeException(e);
        }
    }

    public static Network read(Path xmlFile) {
        try (InputStream is = Files.newInputStream(xmlFile)) {
            return read(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static Network validateAndRead(Path xmlFile) {
        validateWithExtensions(xmlFile);
        return read(xmlFile);
    }

    public static void update(Network network, InputStream is) {
        try {
            XMLStreamReader reader = XML_INPUT_FACTORY_SUPPLIER.get().createXMLStreamReader(is);
            reader.next();

            final VoltageLevel[] vl = new VoltageLevel[1];

            XmlUtil.readUntilEndElement(NETWORK_ROOT_ELEMENT_NAME, reader, () -> {

                switch (reader.getLocalName()) {
                case VoltageLevelXml.ROOT_ELEMENT_NAME: {
                    String id = reader.getAttributeValue(null, "id");
                    vl[0] = network.getVoltageLevel(id);
                    if (vl[0] == null) {
                        throw new RuntimeException("Voltage level '" + id + "' not found");
                    }
                    break;
                }

                case BusXml.ROOT_ELEMENT_NAME: {
                    String id = reader.getAttributeValue(null, "id");
                    float v = XmlUtil.readFloatAttribute(reader, "v");
                    float angle = XmlUtil.readFloatAttribute(reader, "angle");
                    Bus b = vl[0].getBusBreakerView().getBus(id);
                    if (b == null) {
                        b = vl[0].getBusView().getBus(id);
                    }
                    b.setV(v > 0 ? v : Float.NaN).setAngle(angle);
                    break;
                }

                case GeneratorXml.ROOT_ELEMENT_NAME:
                case LoadXml.ROOT_ELEMENT_NAME:
                case ShuntXml.ROOT_ELEMENT_NAME:
                case DanglingLineXml.ROOT_ELEMENT_NAME:
                case LccConverterStationXml.ROOT_ELEMENT_NAME:
                case VscConverterStationXml.ROOT_ELEMENT_NAME: {
                    String id = reader.getAttributeValue(null, "id");
                    float p = XmlUtil.readOptionalFloatAttribute(reader, "p");
                    float q = XmlUtil.readOptionalFloatAttribute(reader, "q");
                    SingleTerminalConnectable inj = (SingleTerminalConnectable) network.getIdentifiable(id);
                    inj.getTerminal().setP(p).setQ(q);
                    break;
                }

                case LineXml.ROOT_ELEMENT_NAME:
                case TwoWindingsTransformerXml.ROOT_ELEMENT_NAME: {
                    String id = reader.getAttributeValue(null, "id");
                    float p1 = XmlUtil.readOptionalFloatAttribute(reader, "p1");
                    float q1 = XmlUtil.readOptionalFloatAttribute(reader, "q1");
                    float p2 = XmlUtil.readOptionalFloatAttribute(reader, "p2");
                    float q2 = XmlUtil.readOptionalFloatAttribute(reader, "q2");
                    TwoTerminalsConnectable branch = (TwoTerminalsConnectable) network.getIdentifiable(id);
                    branch.getTerminal1().setP(p1).setQ(q1);
                    branch.getTerminal2().setP(p2).setQ(q2);
                    break;
                }

                case HvdcLineXml.ROOT_ELEMENT_NAME: {
                    // Nothing to do
                }

                case ThreeWindingsTransformerXml.ROOT_ELEMENT_NAME:
                    throw new AssertionError();
                }
            });
        } catch (XMLStreamException e) {
            throw new RuntimeException(e);
        }
    }

    public static void update(Network network, Path xmlFile) {
        try (InputStream is = Files.newInputStream(xmlFile)) {
            update(network, is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static byte[] gzip(Network network) {
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        try (GZIPOutputStream gzos = new GZIPOutputStream(bos)) {
            NetworkXml.write(network, gzos);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        return bos.toByteArray();
    }

    public static Network gunzip(byte[] networkXmlGz) {
        try (InputStream is = new GZIPInputStream(new ByteArrayInputStream(networkXmlGz))) {
            return NetworkXml.read(is);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Deep copy of the network using XML converter.
     * @param network the network to copy
     * @return the copy of the network
     */
    public static Network copy(Network network) {
        return gunzip(gzip(network));
    }
}