UcteGlskDocument.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.glsk.ucte;

import com.powsybl.glsk.api.GlskDocument;
import com.powsybl.glsk.api.GlskPoint;
import com.powsybl.glsk.commons.GlskException;
import org.threeten.extra.Interval;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.time.Instant;
import java.util.*;

/**
 * Ucte type GLSK document object: contains a list of GlskPoint
 * @author Pengbo Wang {@literal <pengbo.wang@rte-international.com>}
 * @author Amira Kahya {@literal <amira.kahya@rte-france.com>}
 */
public final class UcteGlskDocument implements GlskDocument {
    /**
     * list of GlskPoint in the give Glsk document
     */
    private final List<GlskPoint> listUcteGlskBlocks;
    /**
     * map of Country EIC and time series
     */
    private final Map<String, UcteGlskSeries> ucteGlskSeriesByCountry; //map<countryEICode, UcteGlskSeries>
    /**
     * List of Glsk point by country code
     */
    private final Map<String, List<UcteGlskPoint>> ucteGlskPointsByCountry; //map <CountryID, List<GlskPoint>>
    /**
     * document GSKTimeInterval
     */
    private Interval gSKTimeInterval; // GSKTimeInterval. ex: <GSKTimeInterval v="2016-07-28T22:00Z/2016-07-29T22:00Z"/>

    public static UcteGlskDocument importGlsk(InputStream inputStream) {
        try {
            DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
            documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
            documentBuilderFactory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
            documentBuilderFactory.setAttribute(XMLConstants.FEATURE_SECURE_PROCESSING, Boolean.TRUE);
            documentBuilderFactory.setNamespaceAware(true);

            Document document = documentBuilderFactory.newDocumentBuilder().parse(inputStream);
            document.getDocumentElement().normalize();
            return new UcteGlskDocument(document);
        } catch (IOException | SAXException | ParserConfigurationException e) {
            throw new GlskException("Unable to import CIM GLSK file.", e);
        }
    }

    public static UcteGlskDocument importGlsk(Document document) {
        return new UcteGlskDocument(document);
    }

    private UcteGlskDocument(Document document) {
        if (document.getElementsByTagName("GSKTimeInterval").getLength() > 0) {
            this.gSKTimeInterval = Interval.parse(((Element) document.getElementsByTagName("GSKTimeInterval").item(0)).getAttribute("v"));
        }

        List<UcteGlskSeries> rawlistUcteGlskSeries = new ArrayList<>();
        ucteGlskSeriesByCountry = new HashMap<>();
        NodeList glskSeriesNodeList = document.getElementsByTagName("GSKSeries");
        for (int i = 0; i < glskSeriesNodeList.getLength(); i++) {
            if (glskSeriesNodeList.item(i).getNodeType() == Node.ELEMENT_NODE) {
                Element glskSeriesElement = (Element) glskSeriesNodeList.item(i);
                UcteGlskSeries glskSeries = new UcteGlskSeries(glskSeriesElement);
                rawlistUcteGlskSeries.add(glskSeries);
            }
        }

        //construct ucteGlskSeriesByCountry, merging LSK and GSK for same TimeInterval
        for (UcteGlskSeries glskSeries : rawlistUcteGlskSeries) {
            String currentArea = glskSeries.getArea();
            if (!ucteGlskSeriesByCountry.containsKey(currentArea)) {
                ucteGlskSeriesByCountry.put(currentArea, glskSeries);
            } else {
                UcteGlskSeries ucteGlskSeries = calculateUcteGlskSeries(glskSeries, ucteGlskSeriesByCountry.get(currentArea));
                ucteGlskSeriesByCountry.put(currentArea, ucteGlskSeries);
            }
        }

        //construct list gsk points
        listUcteGlskBlocks = new ArrayList<>();
        ucteGlskSeriesByCountry.keySet().forEach(id -> listUcteGlskBlocks.addAll(ucteGlskSeriesByCountry.get(id).getUcteGlskBlocks()));

        //construct map ucteGlskPointsByCountry
        ucteGlskPointsByCountry = new HashMap<>();
        ucteGlskSeriesByCountry.keySet().forEach(id -> {
            String country = ucteGlskSeriesByCountry.get(id).getArea();
            List<UcteGlskPoint> glskPointList = ucteGlskSeriesByCountry.get(id).getUcteGlskBlocks();
            if (ucteGlskPointsByCountry.containsKey(country)) {
                glskPointList.addAll(ucteGlskPointsByCountry.get(country));
            }
            ucteGlskPointsByCountry.put(country, glskPointList);
        });
    }

    /**
     * merge LSK and GSK of the same country and Same Point Interval
     * and add glsk Point for missed  intervals
     * @param incomingSeries incoming time series to be merged with old time series
     * @param oldSeries      current time series to be updated
     * @return Glsk series
     */
    private UcteGlskSeries calculateUcteGlskSeries(UcteGlskSeries incomingSeries, UcteGlskSeries oldSeries) {
        List<UcteGlskPoint> glskPointListTobeAdded = new ArrayList<>();
        List<Interval> oldPointsIntervalsList = new ArrayList<>();
        List<UcteGlskPoint> incomingPoints = incomingSeries.getUcteGlskBlocks();
        List<UcteGlskPoint> oldPoints = oldSeries.getUcteGlskBlocks();
        for (UcteGlskPoint incomingPoint : incomingPoints) {
            boolean alreadyExists = false;
            for (UcteGlskPoint oldPoint : oldPoints) {
                if (oldPoint.getPointInterval().equals(incomingPoint.getPointInterval())) {
                    oldPoint.getGlskShiftKeys().addAll(incomingPoint.getGlskShiftKeys());
                    alreadyExists = true;
                    break;
                }
            }
            if (!alreadyExists) {
                glskPointListTobeAdded.add(incomingPoint);
            }
        }

        oldPoints.forEach(oldPoint -> oldPointsIntervalsList.add(oldPoint.getPointInterval()));
        glskPointListTobeAdded.forEach(glskPointToBeAdded -> {
            if (!oldPointsIntervalsList.contains(glskPointToBeAdded.getPointInterval())) {
                oldPoints.add(glskPointToBeAdded);
            }
        });
        return oldSeries;
    }

    /**
     * @return getter list of glsk point in the document
     */
    public List<GlskPoint> getListUcteGlskBlocks() {
        return listUcteGlskBlocks;
    }

    /**
     * @return getter list of time series
     */
    public List<UcteGlskSeries> getListGlskSeries() {
        return new ArrayList<>(ucteGlskSeriesByCountry.values());
    }

    /**
     * @return getter map of list glsk point
     */
    public Map<String, List<UcteGlskPoint>> getUcteGlskPointsByCountry() {
        return ucteGlskPointsByCountry;
    }

    /**
     * @return getter list of country
     */
    @Override
    public List<String> getZones() {
        return new ArrayList<>(ucteGlskPointsByCountry.keySet());
    }

    @Override
    public List<GlskPoint> getGlskPoints(String zone) {
        return new ArrayList<>(getUcteGlskPointsByCountry().get(zone));
    }

    public Map<String, UcteGlskPoint> getGlskPointsForInstant(Instant instant) {
        Map<String, UcteGlskPoint> glskPointInstant = new HashMap<>();
        ucteGlskPointsByCountry.forEach((key, glskPoints) -> {
            UcteGlskPoint glskPoint = glskPoints.stream()
                    .filter(p -> p.containsInstant(instant))
                    .findAny().orElseThrow(() -> new GlskException("Error during get glsk point by instant for " + key + " country"));
            glskPointInstant.put(key, glskPoint);
        });
        return glskPointInstant;
    }

    /**
     * @return document time interval
     */
    public Interval getGSKTimeInterval() {
        return gSKTimeInterval;
    }
}