CracDeserializer.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.openrao.data.crac.io.json.deserializers;

import com.powsybl.iidm.network.Network;
import com.powsybl.openrao.commons.OpenRaoException;
import com.powsybl.openrao.data.crac.io.json.ExtensionsHandler;
import com.powsybl.openrao.data.crac.io.json.JsonSerializationConstants;
import com.powsybl.openrao.data.crac.api.Crac;
import com.powsybl.openrao.data.crac.api.CracFactory;
import com.powsybl.openrao.data.crac.api.InstantKind;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.powsybl.commons.extensions.Extension;
import com.powsybl.commons.json.JsonUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.time.OffsetDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author Joris Mancini {@literal <joris.mancini at rte-france.com>}
 * @author Baptiste Seguinot {@literal <baptiste.seguinot at rte-france.com>}
 */
public class CracDeserializer extends JsonDeserializer<Crac> {

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

    private CracFactory cracFactory;

    private Network network;

    private final boolean headerCheckOnly;

    public CracDeserializer(boolean headerCheckOnly) {
        this.headerCheckOnly = headerCheckOnly;
    }

    public CracDeserializer(CracFactory cracFactory, Network network) {
        this.cracFactory = cracFactory;
        this.network = network;
        this.headerCheckOnly = false;
    }

    @Override
    public Crac deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {

        // check header
        String version = isValid(jsonParser);
        if (headerCheckOnly) {
            return null;
        }

        // get id, name and timestamp
        scrollJsonUntilField(jsonParser, JsonSerializationConstants.ID);
        String id = jsonParser.nextTextValue();
        jsonParser.nextToken();
        if (!jsonParser.getCurrentName().equals(JsonSerializationConstants.NAME)) {
            throw new OpenRaoException(String.format("The JSON Crac must contain a %s field after the %s field", JsonSerializationConstants.NAME, JsonSerializationConstants.ID));
        }
        String name = jsonParser.nextTextValue();
        JsonToken nextToken = jsonParser.nextToken();
        OffsetDateTime timestamp;
        if (jsonParser.getCurrentName().equals(JsonSerializationConstants.TIMESTAMP)) {
            timestamp = OffsetDateTime.parse(jsonParser.nextTextValue());
            nextToken = jsonParser.nextToken();
        } else {
            timestamp = null;
        }
        Crac crac = cracFactory.create(id, name, timestamp);
        if (JsonSerializationConstants.getPrimaryVersionNumber(version) < 2) {
            crac.newInstant("preventive", InstantKind.PREVENTIVE)
                .newInstant("outage", InstantKind.OUTAGE)
                .newInstant("auto", InstantKind.AUTO)
                .newInstant("curative", InstantKind.CURATIVE);
        }

        Map<String, String> deserializedNetworkElementsNamesPerId = null;

        // deserialize the following lines of the Crac
        while (nextToken != JsonToken.END_OBJECT) {
            switch (jsonParser.getCurrentName()) {
                case JsonSerializationConstants.NETWORK_ELEMENTS_NAME_PER_ID:
                    jsonParser.nextToken();
                    deserializedNetworkElementsNamesPerId = jsonParser.readValueAs(HashMap.class);
                    break;

                case JsonSerializationConstants.CONTINGENCIES:
                    jsonParser.nextToken();
                    ContingencyArrayDeserializer.deserialize(jsonParser, crac, network);
                    break;

                case JsonSerializationConstants.FLOW_CNECS:
                    jsonParser.nextToken();
                    FlowCnecArrayDeserializer.deserialize(jsonParser, deserializationContext, version, crac, deserializedNetworkElementsNamesPerId);
                    break;

                case JsonSerializationConstants.ANGLE_CNECS:
                    jsonParser.nextToken();
                    AngleCnecArrayDeserializer.deserialize(jsonParser, deserializationContext, version, crac, deserializedNetworkElementsNamesPerId);
                    break;

                case JsonSerializationConstants.VOLTAGE_CNECS:
                    jsonParser.nextToken();
                    VoltageCnecArrayDeserializer.deserialize(jsonParser, deserializationContext, version, crac, deserializedNetworkElementsNamesPerId);
                    break;

                case JsonSerializationConstants.PST_RANGE_ACTIONS:
                    jsonParser.nextToken();
                    PstRangeActionArrayDeserializer.deserialize(jsonParser, version, crac, deserializedNetworkElementsNamesPerId, network);
                    break;

                case JsonSerializationConstants.HVDC_RANGE_ACTIONS:
                    jsonParser.nextToken();
                    HvdcRangeActionArrayDeserializer.deserialize(jsonParser, version, crac, deserializedNetworkElementsNamesPerId);
                    break;

                case JsonSerializationConstants.INJECTION_RANGE_ACTIONS:
                    jsonParser.nextToken();
                    InjectionRangeActionArrayDeserializer.deserialize(jsonParser, version, crac, deserializedNetworkElementsNamesPerId);
                    break;

                case JsonSerializationConstants.COUNTER_TRADE_RANGE_ACTIONS:
                    jsonParser.nextToken();
                    CounterTradeRangeActionArrayDeserializer.deserialize(jsonParser, version, crac, deserializedNetworkElementsNamesPerId);
                    break;

                case JsonSerializationConstants.NETWORK_ACTIONS:
                    jsonParser.nextToken();
                    NetworkActionArrayDeserializer.deserialize(jsonParser, version, crac, deserializedNetworkElementsNamesPerId, network);
                    break;
                case JsonSerializationConstants.EXTENSIONS:
                    jsonParser.nextToken();
                    List<Extension<Crac>> extensions = JsonUtil.readExtensions(jsonParser, deserializationContext, ExtensionsHandler.getExtensionsSerializers());
                    ExtensionsHandler.getExtensionsSerializers().addExtensions(crac, extensions);
                    break;

                case JsonSerializationConstants.INSTANTS:
                    jsonParser.nextToken();
                    InstantArrayDeserializer.deserialize(jsonParser, crac);
                    break;

                case JsonSerializationConstants.RA_USAGE_LIMITS_PER_INSTANT:
                    jsonParser.nextToken();
                    RaUsageLimitsDeserializer.deserialize(jsonParser, crac);
                    break;

                default:
                    throw new OpenRaoException("Unexpected field in Crac: " + jsonParser.getCurrentName());
            }
            nextToken = jsonParser.nextToken();
        }
        return crac;
    }

    public static String isValid(JsonParser jsonParser) throws IOException {
        if (!jsonParser.nextFieldName().equals(JsonSerializationConstants.TYPE)) {
            throw new OpenRaoException(String.format("json CRAC must start with field %s", JsonSerializationConstants.TYPE));
        }
        if (!jsonParser.nextTextValue().equals(JsonSerializationConstants.CRAC_TYPE)) {
            throw new OpenRaoException(String.format("type of document must be %s", JsonSerializationConstants.CRAC_TYPE));
        }
        if (!jsonParser.nextFieldName().equals(JsonSerializationConstants.VERSION)) {
            throw new OpenRaoException(String.format("%s must contain a %s in its second field", JsonSerializationConstants.CRAC_TYPE, JsonSerializationConstants.VERSION));
        }
        String version = jsonParser.nextTextValue();
        checkVersion(version);
        jsonParser.nextToken();
        return version;
    }

    private void scrollJsonUntilField(JsonParser jsonParser, String field) throws IOException {
        while (!jsonParser.getCurrentName().equals(field)) {
            if (jsonParser.nextToken() == JsonToken.END_OBJECT) {
                throw new OpenRaoException(String.format("The JSON Crac must contain an %s field", field));
            }
            jsonParser.nextToken();
        }
    }

    private static void checkVersion(String cracVersion) {

        if (JsonSerializationConstants.getPrimaryVersionNumber(JsonSerializationConstants.CRAC_IO_VERSION) > JsonSerializationConstants.getPrimaryVersionNumber(cracVersion)) {
            LOGGER.warn("CRAC importer {} might not be longer compatible with json CRAC version {}, consider updating your json CRAC file", JsonSerializationConstants.CRAC_IO_VERSION, cracVersion);
        }
        if (JsonSerializationConstants.getPrimaryVersionNumber(JsonSerializationConstants.CRAC_IO_VERSION) < JsonSerializationConstants.getPrimaryVersionNumber(cracVersion)) {
            throw new OpenRaoException(String.format("CRAC importer %s cannot handle json CRAC version %s, consider upgrading open-rao version", JsonSerializationConstants.CRAC_IO_VERSION, cracVersion));
        }
        if (JsonSerializationConstants.getSubVersionNumber(JsonSerializationConstants.CRAC_IO_VERSION) < JsonSerializationConstants.getSubVersionNumber(cracVersion)) {
            LOGGER.warn("CRAC importer {} might not be compatible with json CRAC version {}, consider upgrading open-rao version", JsonSerializationConstants.CRAC_IO_VERSION, cracVersion);
        }

        // otherwise, all is good !
    }
}