TimeSeriesMetadata.java
/**
* Copyright (c) 2017, 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.timeseries;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.JsonToken;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Objects;
/**
* @author Geoffroy Jamgotchian {@literal <geoffroy.jamgotchian at rte-france.com>}
*/
public class TimeSeriesMetadata {
private final String name;
private final TimeSeriesDataType dataType;
private final Map<String, String> tags;
private final TimeSeriesIndex index;
public TimeSeriesMetadata(String name, TimeSeriesDataType dataType, TimeSeriesIndex index) {
// The order of the Map.of() elements is not constant/predictable so we have to build an unmodifiableMap LinkedHashMap ourselves
this(name, dataType, Collections.unmodifiableMap(new LinkedHashMap<>()), index);
}
public TimeSeriesMetadata(String name, TimeSeriesDataType dataType, Map<String, String> tags, TimeSeriesIndex index) {
this.name = Objects.requireNonNull(name);
this.dataType = Objects.requireNonNull(dataType);
this.tags = Collections.unmodifiableMap(Objects.requireNonNull(tags));
this.index = Objects.requireNonNull(index);
}
public String getName() {
return name;
}
public TimeSeriesDataType getDataType() {
return dataType;
}
public Map<String, String> getTags() {
return tags;
}
public TimeSeriesIndex getIndex() {
return index;
}
public void writeJson(JsonGenerator generator) {
try {
generator.writeStartObject();
generator.writeStringField("name", name);
generator.writeStringField("dataType", dataType.name());
generator.writeFieldName("tags");
generator.writeStartArray();
for (Map.Entry<String, String> e : tags.entrySet()) {
generator.writeStartObject();
generator.writeStringField(e.getKey(), e.getValue());
generator.writeEndObject();
}
generator.writeEndArray();
generator.writeFieldName(index.getType());
index.writeJson(generator);
generator.writeEndObject();
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
private static final class JsonParsingContext {
private String name;
private TimeSeriesDataType dataType;
private final Map<String, String> tags = new LinkedHashMap<>();
private TimeSeriesIndex index;
private boolean insideTags = false;
public boolean isComplete() {
return name != null && dataType != null && index != null;
}
}
static void parseFieldName(JsonParser parser, JsonParsingContext context, TimeSeries.TimeFormat timeFormat) throws IOException {
String fieldName = parser.currentName();
if (context.insideTags) {
context.tags.put(fieldName, parser.nextTextValue());
} else {
switch (fieldName) {
case "metadata" -> {
// Do nothing
}
case "name" -> context.name = parser.nextTextValue();
case "dataType" -> context.dataType = TimeSeriesDataType.valueOf(parser.nextTextValue());
case "tags" -> context.insideTags = true;
case RegularTimeSeriesIndex.TYPE -> context.index = RegularTimeSeriesIndex.parseJson(parser);
case IrregularTimeSeriesIndex.TYPE -> context.index = IrregularTimeSeriesIndex.parseJson(parser,
timeFormat == TimeSeries.TimeFormat.MILLIS ? TimeSeriesIndex.ExportFormat.MILLISECONDS : TimeSeriesIndex.ExportFormat.NANOSECONDS);
case InfiniteTimeSeriesIndex.TYPE -> context.index = InfiniteTimeSeriesIndex.parseJson(parser);
default -> throw new IllegalStateException("Unexpected field name " + fieldName);
}
}
}
public static TimeSeriesMetadata parseJson(JsonParser parser) {
return parseJson(parser, TimeSeries.TimeFormat.MILLIS);
}
public static TimeSeriesMetadata parseJson(JsonParser parser, TimeSeries.TimeFormat timeFormat) {
try {
JsonToken token;
JsonParsingContext context = new JsonParsingContext();
while ((token = parser.nextToken()) != null) {
switch (token) {
case FIELD_NAME -> parseFieldName(parser, context, timeFormat);
case END_ARRAY -> {
if (context.insideTags) {
context.insideTags = false;
}
}
case END_OBJECT -> {
if (!context.insideTags) {
if (context.isComplete()) {
return new TimeSeriesMetadata(context.name, context.dataType, context.tags, context.index);
} else {
throw new IllegalStateException("Incomplete time series metadata json");
}
}
}
default -> {
// Do nothing
}
}
}
throw new IllegalStateException("should not happen");
} catch (IOException e) {
throw new UncheckedIOException(e);
}
}
@Override
public int hashCode() {
return Objects.hash(name, dataType, tags, index);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TimeSeriesMetadata other) {
return name.equals(other.name) &&
dataType == other.dataType &&
tags.equals(other.tags) &&
index.equals(other.index);
}
return false;
}
@Override
public String toString() {
return "TimeSeriesMetadata(name=" + name + ", dataType=" + dataType + ", tags=" + tags + ", index=" + index + ")";
}
}