DynamicSimulationResultDeserializer.java

/**
 * Copyright (c) 2019, 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.dynamicsimulation.json;

import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import com.fasterxml.jackson.databind.DeserializationContext;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.powsybl.commons.json.JsonUtil;
import com.powsybl.dynamicsimulation.DynamicSimulationResult;
import com.powsybl.dynamicsimulation.DynamicSimulationResultImpl;
import com.powsybl.dynamicsimulation.TimelineEvent;
import com.powsybl.timeseries.DoubleTimeSeries;
import com.powsybl.timeseries.json.DoubleTimeSeriesJsonDeserializer;

import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

/**
 * @author Marcos de Miguel {@literal <demiguelm at aia.es>}
 */
public class DynamicSimulationResultDeserializer extends StdDeserializer<DynamicSimulationResult> {

    private final DoubleTimeSeriesJsonDeserializer doubleTimeSeriesJsonDeserializer = new DoubleTimeSeriesJsonDeserializer();

    DynamicSimulationResultDeserializer() {
        super(DynamicSimulationResult.class);
    }

    @Override
    public DynamicSimulationResult deserialize(JsonParser parser, DeserializationContext ctx) throws IOException {
        DynamicSimulationResult.Status status = null;
        String error = "";
        Map<String, DoubleTimeSeries> curves = new LinkedHashMap<>();
        Map<String, Double> fsv = new LinkedHashMap<>();
        List<TimelineEvent> timeLine = new ArrayList<>();

        while (parser.nextToken() != JsonToken.END_OBJECT) {
            switch (parser.currentName()) {
                case "version" -> parser.nextToken(); // skip
                case "status" -> {
                    parser.nextToken();
                    status = parser.readValueAs(DynamicSimulationResult.Status.class);
                }
                case "error" -> {
                    parser.nextToken();
                    error = parser.readValueAs(String.class);
                }
                case "curves" -> {
                    parser.nextToken();
                    deserializeCurves(parser, curves);
                }
                case "finalStateValues" -> {
                    parser.nextToken();
                    deserializeFinalStateValues(parser, fsv);
                }
                case "timeLine" -> {
                    parser.nextToken();
                    deserializeTimeline(parser, timeLine);
                }
                default -> throw getUnexpectedFieldException(parser);
            }
        }

        return new DynamicSimulationResultImpl(status, error, curves, fsv, timeLine);
    }

    private void deserializeCurves(JsonParser parser, Map<String, DoubleTimeSeries> curves) throws IOException {
        DoubleTimeSeries curve;
        while (parser.nextToken() != JsonToken.END_ARRAY) {
            curve = doubleTimeSeriesJsonDeserializer.deserialize(parser, null);
            if (curve != null) {
                curves.put(curve.getMetadata().getName(), curve);
            }
        }
    }

    private void deserializeFinalStateValues(JsonParser parser, Map<String, Double> fsvs) throws IOException {
        while (parser.nextToken() != JsonToken.END_ARRAY) {
            String name = null;
            double value = 0;
            while (parser.nextToken() != JsonToken.END_OBJECT) {
                switch (parser.currentName()) {
                    case "name" -> name = parser.getValueAsString();
                    case "value" -> value = parser.getValueAsDouble();
                    default -> throw getUnexpectedFieldException(parser);
                }
            }
            fsvs.put(name, value);
        }
    }

    private void deserializeTimeline(JsonParser parser, List<TimelineEvent> timelineEvents) throws IOException {
        while (parser.nextToken() != JsonToken.END_ARRAY) {
            timelineEvents.add(deserializeTimelineEvent(parser));
        }
    }

    private TimelineEvent deserializeTimelineEvent(JsonParser parser) throws IOException {
        double time = 0;
        String modelName = null;
        String message = null;
        while (parser.nextToken() != JsonToken.END_OBJECT) {
            switch (parser.currentName()) {
                case "time" -> time = parser.getValueAsDouble();
                case "modelName" -> modelName = parser.getValueAsString();
                case "message" -> message = parser.getValueAsString();
                default -> throw getUnexpectedFieldException(parser);
            }
        }
        return new TimelineEvent(time, modelName, message);
    }

    public static DynamicSimulationResult read(InputStream is) throws IOException {
        Objects.requireNonNull(is);
        ObjectMapper objectMapper = JsonUtil.createObjectMapper();

        SimpleModule module = new SimpleModule();
        module.addDeserializer(DynamicSimulationResult.class, new DynamicSimulationResultDeserializer());
        objectMapper.registerModule(module);

        return objectMapper.readValue(is, DynamicSimulationResult.class);
    }

    public static DynamicSimulationResult read(Path jsonFile) {
        Objects.requireNonNull(jsonFile);
        try (InputStream is = Files.newInputStream(jsonFile)) {
            return read(is);
        } catch (IOException e) {
            throw new UncheckedIOException(e);
        }
    }

    private static IllegalStateException getUnexpectedFieldException(JsonParser parser) throws IOException {
        return new IllegalStateException("Unexpected field: " + parser.currentName());
    }
}