diff --git a/io.openems.common/src/io/openems/common/jsonrpc/base/GenericJsonrpcRequest.java b/io.openems.common/src/io/openems/common/jsonrpc/base/GenericJsonrpcRequest.java index ded47a30a04..d91ecd7ef51 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/base/GenericJsonrpcRequest.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/base/GenericJsonrpcRequest.java @@ -6,6 +6,7 @@ import com.google.gson.JsonObject; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.utils.JsonUtils; /** @@ -26,6 +27,23 @@ */ public class GenericJsonrpcRequest extends JsonrpcRequest { + /** + * Creates a GenericJsonrpcRequest from the provided object. + * + * @param the original type of the request + * @param endpointType the definition of the endpoint + * @param request the request to transform into a + * {@link GenericJsonrpcRequest} + * @return the created {@link GenericJsonrpcRequest} + */ + public static GenericJsonrpcRequest createRequest(// + EndpointRequestType endpointType, // + REQUEST request // + ) { + return new GenericJsonrpcRequest(endpointType.getMethod(), + endpointType.getRequestSerializer().serialize(request).getAsJsonObject()); + } + /** * Parses the String to a {@link GenericJsonrpcRequest}. * diff --git a/io.openems.common/src/io/openems/common/jsonrpc/request/GetEdgesRequest.java b/io.openems.common/src/io/openems/common/jsonrpc/request/GetEdgesRequest.java index 40d64c02b65..6cdba21915f 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/request/GetEdgesRequest.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/request/GetEdgesRequest.java @@ -1,6 +1,10 @@ package io.openems.common.jsonrpc.request; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonArray; import static java.util.Collections.emptyList; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; import java.util.ArrayList; import java.util.List; @@ -15,6 +19,8 @@ import io.openems.common.exceptions.OpenemsException; import io.openems.common.function.ThrowingFunction; import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.serialization.JsonElementPath; +import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; /** @@ -50,12 +56,58 @@ public static class PaginationOptions { private final SearchParams searchParams; private final String query; + /** + * Returns a {@link JsonSerializer} for a + * {@link GetEdgesRequest.PaginationOptions.SearchParams}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(GetEdgesRequest.PaginationOptions.class, // + json -> new GetEdgesRequest.PaginationOptions(// + json.getInt("page"), // + json.getOptionalInt("limit").orElse(DEFAULT_LIMIT), // + json.getString("query"), // + json.getObject("searchParams", SearchParams.serializer())), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("page", obj.page) // + .addProperty("limit", obj.limit) // + .addProperty("query", obj.query) // + .add("searchParams", SearchParams.serializer().serialize(obj.searchParams)) // + .build()); + } + public record SearchParams(// List productTypes, // List sumStates, // boolean searchIsOnline, // boolean isOnline // ) { + /** + * Returns a {@link JsonSerializer} for a + * {@link GetEdgesRequest.PaginationOptions.SearchParams}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(GetEdgesRequest.PaginationOptions.SearchParams.class, json -> { + final var isOnline = json.getBooleanPathNullable("isOnline"); + return new GetEdgesRequest.PaginationOptions.SearchParams(// + json.getList("producttype", JsonElementPath::getAsString), // + json.getJsonArrayPath("sumState").collect(mapping(t -> t.getAsEnum(Level.class), toList())), // + isOnline.isPresent(), // + isOnline.getOrDefault(false)); + }, obj -> JsonUtils.buildJsonObject() // + .add("producttype", obj.productTypes().stream() // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) + .add("sumState", obj.sumStates().stream() // + .map(Level::name) // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) + .onlyIf(obj.searchIsOnline(), t -> t.addProperty("isOnline", obj.isOnline())) // + .build()); + } /** * Creates a {@link SearchParams} from a {@link JsonObject}. @@ -119,7 +171,7 @@ public static PaginationOptions from(JsonObject params) throws OpenemsNamedExcep return new PaginationOptions(page, limit, query, searchParams); } - private PaginationOptions(int page, int limit, String query, SearchParams searchParams) { + public PaginationOptions(int page, int limit, String query, SearchParams searchParams) { this.page = page; this.limit = limit; this.query = query; diff --git a/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesDataRequest.java b/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesDataRequest.java index d28ecc073b5..6f78d0ff36e 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesDataRequest.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesDataRequest.java @@ -6,6 +6,7 @@ import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.Optional; +import java.util.Set; import java.util.TimeZone; import java.util.TreeSet; @@ -96,7 +97,7 @@ public static QueryHistoricTimeseriesDataRequest from(JsonrpcRequest r) throws O private final ZonedDateTime fromDate; private final ZonedDateTime toDate; - private final TreeSet channels = new TreeSet<>(); + private final Set channels; /** * Resolution of the data or empty for automatic. @@ -104,21 +105,23 @@ public static QueryHistoricTimeseriesDataRequest from(JsonrpcRequest r) throws O private final Optional resolution; private QueryHistoricTimeseriesDataRequest(JsonrpcRequest request, ZonedDateTime fromDate, ZonedDateTime toDate, - Optional resolution) throws OpenemsNamedException { + Optional resolution) { super(request, QueryHistoricTimeseriesDataRequest.METHOD); this.fromDate = fromDate; this.toDate = toDate; this.resolution = resolution; + this.channels = new TreeSet<>(); } public QueryHistoricTimeseriesDataRequest(ZonedDateTime fromDate, ZonedDateTime toDate, - Optional resolution) throws OpenemsNamedException { + Optional resolution, Set channels) { super(QueryHistoricTimeseriesDataRequest.METHOD); this.fromDate = fromDate; this.toDate = toDate; this.resolution = resolution; + this.channels = channels; } private void addChannel(ChannelAddress address) { @@ -170,7 +173,7 @@ public ZonedDateTime getToDate() { * * @return Set of {@link ChannelAddress} */ - public TreeSet getChannels() { + public Set getChannels() { return this.channels; } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesExportXlxsRequest.java b/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesExportXlxsRequest.java index 98a131ec52d..d4d9a2a97c7 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesExportXlxsRequest.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/request/QueryHistoricTimeseriesExportXlxsRequest.java @@ -66,6 +66,16 @@ private QueryHistoricTimeseriesExportXlxsRequest(JsonrpcRequest request, ZonedDa this.toDate = toDate; } + public QueryHistoricTimeseriesExportXlxsRequest(ZonedDateTime fromDate, ZonedDateTime toDate) + throws OpenemsNamedException { + super(QueryHistoricTimeseriesExportXlxsRequest.METHOD); + + DateUtils.assertSameTimezone(fromDate, toDate); + this.timezoneDiff = ZoneOffset.from(fromDate).getTotalSeconds(); + this.fromDate = fromDate; + this.toDate = toDate; + } + @Override public JsonObject getParams() { return JsonUtils.buildJsonObject() // diff --git a/io.openems.common/src/io/openems/common/jsonrpc/request/UpdateComponentConfigRequest.java b/io.openems.common/src/io/openems/common/jsonrpc/request/UpdateComponentConfigRequest.java index 3045fbacc1c..04696443469 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/request/UpdateComponentConfigRequest.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/request/UpdateComponentConfigRequest.java @@ -1,5 +1,7 @@ package io.openems.common.jsonrpc.request; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + import java.util.ArrayList; import java.util.List; @@ -10,6 +12,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.base.JsonrpcRequest; +import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; /** @@ -106,6 +109,23 @@ protected static List from(JsonArray j) throws OpenemsNamedException { return properties; } + /** + * Returns a {@link JsonSerializer} for a + * {@link UpdateComponentConfigRequest.Property}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(UpdateComponentConfigRequest.Property.class, // + json -> new UpdateComponentConfigRequest.Property(// + json.getString("name"), // + json.getJsonElement("value")), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("name", obj.name) // + .add("value", obj.value) // + .build()); + } + private final String name; private final JsonElement value; diff --git a/io.openems.common/src/io/openems/common/jsonrpc/response/GetEdgesResponse.java b/io.openems.common/src/io/openems/common/jsonrpc/response/GetEdgesResponse.java index 85abf272563..bd2a679322e 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/response/GetEdgesResponse.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/response/GetEdgesResponse.java @@ -1,5 +1,7 @@ package io.openems.common.jsonrpc.response; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + import java.time.ZonedDateTime; import java.util.List; import java.util.UUID; @@ -10,6 +12,7 @@ import io.openems.common.channel.Level; import io.openems.common.jsonrpc.base.JsonrpcResponseSuccess; import io.openems.common.jsonrpc.request.GetEdgesRequest; +import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.session.Role; import io.openems.common.types.SemanticVersion; import io.openems.common.utils.JsonUtils; @@ -41,6 +44,36 @@ public record EdgeMetadata(// Level sumState // ) { + /** + * Returns a {@link JsonSerializer} for a {@link GetEdgesResponse.EdgeMetadata}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(GetEdgesResponse.EdgeMetadata.class, // + json -> new GetEdgesResponse.EdgeMetadata(// + json.getString("id"), // + json.getString("comment"), // + json.getString("producttype"), // + json.getSemanticVersion("version"), // + json.getEnum("role", Role.class), // + json.getBoolean("isOnline"), // + json.getZonedDateTime("lastmessage"), // + json.getZonedDateTimeOrNull("firstSetupProtocol"), // + json.getEnum("sumState", Level.class)), + obj -> JsonUtils.buildJsonObject() // + .addProperty("id", obj.id()) // + .addProperty("comment", obj.comment()) // + .addProperty("producttype", obj.producttype()) // + .addProperty("version", obj.version().toString()) // + .addProperty("role", obj.role()) // + .addProperty("isOnline", obj.isOnline()) // + .addProperty("lastmessage", obj.lastmessage()) // + .addProperty("firstSetupProtocol", obj.firstSetupProtocol()) // + .addProperty("sumState", obj.sumState()) // + .build()); + } + /** * Converts a collection of EdgeMetadatas to a JsonArray. * diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPath.java new file mode 100644 index 00000000000..208fe191041 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPath.java @@ -0,0 +1,12 @@ +package io.openems.common.jsonrpc.serialization; + +public interface BooleanPath extends JsonPath { + + /** + * Gets the boolean value of the current path. + * + * @return the value + */ + public boolean get(); + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathActual.java new file mode 100644 index 00000000000..0eb3cf66708 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathActual.java @@ -0,0 +1,45 @@ +package io.openems.common.jsonrpc.serialization; + +public final class BooleanPathActual { + + public static final class BooleanPathActualNonNull implements BooleanPath { + + private final boolean value; + + public BooleanPathActualNonNull(boolean value) { + super(); + this.value = value; + } + + @Override + public boolean get() { + return this.value; + } + + } + + public static final class BooleanPathActualNullable implements BooleanPathNullable { + + private final Boolean value; + + public BooleanPathActualNullable(Boolean value) { + super(); + this.value = value; + } + + @Override + public boolean isPresent() { + return this.value != null; + } + + @Override + public Boolean getOrNull() { + return this.value; + } + + } + + private BooleanPathActual() { + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathDummy.java new file mode 100644 index 00000000000..2f8d86b31a1 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathDummy.java @@ -0,0 +1,43 @@ +package io.openems.common.jsonrpc.serialization; + +import com.google.gson.JsonElement; + +public final class BooleanPathDummy { + + public static final class BooleanPathDummyNonNull implements BooleanPath, JsonPathDummy { + + @Override + public boolean get() { + return false; + } + + @Override + public JsonElement buildPath() { + return JsonPrimitivePathDummy.buildPath("boolean", false); + } + + } + + public static final class BooleanPathDummyNullable implements BooleanPathNullable, JsonPathDummy { + + @Override + public boolean isPresent() { + return false; + } + + @Override + public Boolean getOrNull() { + return null; + } + + @Override + public JsonElement buildPath() { + return JsonPrimitivePathDummy.buildPath("boolean", true); + } + + } + + private BooleanPathDummy() { + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathNullable.java new file mode 100644 index 00000000000..77c5568f52e --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/BooleanPathNullable.java @@ -0,0 +1,42 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.Optional; + +public interface BooleanPathNullable { + + /** + * Checks if the current value is present. + * + * @return true if the current value is present; else false + */ + public boolean isPresent(); + + /** + * Gets the boolean value of the current path. + * + * @return the value; or null if not present + */ + public Boolean getOrNull(); + + /** + * Gets the current value if present otherwise returns the provided default + * value. + * + * @param defaultValue the default value to provide if the current values is not + * present + * @return the current value if present; else the default value + */ + public default boolean getOrDefault(boolean defaultValue) { + return this.isPresent() ? this.getOrNull() : defaultValue; + } + + /** + * Gets the current value as a {@link Optional}. + * + * @return a {@link Optional} of the current value + */ + public default Optional getOptional() { + return Optional.ofNullable(this.getOrNull()); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/EmptyObject.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/EmptyObject.java new file mode 100644 index 00000000000..54b9d02cd3b --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/EmptyObject.java @@ -0,0 +1,28 @@ +package io.openems.common.jsonrpc.serialization; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import io.openems.common.utils.JsonUtils; + +public final class EmptyObject { + + public static final EmptyObject INSTANCE = new EmptyObject(); + + /** + * Returns a {@link JsonSerializer} for a {@link EmptyObject}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(EmptyObject.class, json -> { + return EmptyObject.INSTANCE; + }, obj -> { + return JsonUtils.buildJsonObject() // + .build(); + }); + } + + private EmptyObject() { + } + +} diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestType.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/EndpointRequestType.java similarity index 86% rename from io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestType.java rename to io.openems.common/src/io/openems/common/jsonrpc/serialization/EndpointRequestType.java index 21b1085395a..f1734b15191 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestType.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/EndpointRequestType.java @@ -1,6 +1,4 @@ -package io.openems.edge.common.jsonapi; - -import io.openems.common.jsonrpc.serialization.JsonSerializer; +package io.openems.common.jsonrpc.serialization; public interface EndpointRequestType { diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java index 99ad2a70858..606d7c1d42c 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPath.java @@ -1,7 +1,15 @@ package io.openems.common.jsonrpc.serialization; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toSet; +import static java.util.stream.Collectors.toUnmodifiableList; + +import java.lang.reflect.Array; import java.util.List; +import java.util.Set; import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.Collector; import com.google.gson.JsonArray; import com.google.gson.JsonElement; @@ -10,25 +18,93 @@ public interface JsonArrayPath extends JsonPath { /** - * Gets the elements as a list parsed to the object. + * Collects the values of the current {@link JsonArray} with the provided + * {@link Collector}. * - * @param the type of the objects - * @param mapper the {@link JsonElement} to object mapper - * @return the list with the parsed values + * @param the mutable accumulation type of the reduction operation + * (often hidden as an implementation detail) + * @param the result type of the reduction operation + * @param collector the {@link Collector} describing the reduction + * @return the result of the reduction */ - public List getAsList(Function mapper); + public R collect(Collector collector); /** - * Gets the elements as a list parsed to the object. + * Collects all elements into a immutable {@link List}. * - * @param the type of the objects - * @param serializer the {@link JsonSerializer} to deserialize the elements - * @return the list with the parsed values + * @param the type of the elements + * @param mapper the mapper to convert the elements + * @return the result {@link List} containing all {@link JsonElement + * JsonElements} converted by the provided mapper + */ + public default List getAsList(Function mapper) { + return this.collect(mapping(mapper, toUnmodifiableList())); + } + + /** + * Collects all elements into a immutable {@link List}. + * + * @param the type of the elements + * @param serializer the {@link JsonSerializer} to convert the elements + * @return the result {@link List} containing all {@link JsonElement + * JsonElements} converted by the provided {@link JsonSerializer} */ public default List getAsList(JsonSerializer serializer) { return this.getAsList(serializer::deserializePath); } + /** + * Collects all elements into a {@link Set}. + * + * @param the type of the elements + * @param mapper the mapper to convert the elements + * @return the result {@link Set} containing all {@link JsonElement + * JsonElements} converted by the provided mapper + */ + public default Set getAsSet(Function mapper) { + return this.collect(mapping(mapper, toSet())); + } + + /** + * Collects all elements into a {@link Set}. + * + * @param the type of the elements + * @param serializer the {@link JsonSerializer} to convert the elements + * @return the result {@link Set} containing all {@link JsonElement + * JsonElements} converted by the provided {@link JsonSerializer} + */ + public default Set getAsSet(JsonSerializer serializer) { + return this.getAsSet(serializer::deserializePath); + } + + /** + * Collects all elements into a {@link Array}. + * + * @param the type of the elements + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param mapper the mapper to convert the elements + * @return the result {@link Array} containing all {@link JsonElement + * JsonElements} converted by the provided mapper + */ + public default T[] getAsArray(IntFunction generator, Function mapper) { + return this.getAsList(mapper).toArray(generator); + } + + /** + * Collects all elements into a {@link Array}. + * + * @param the type of the elements + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param serializer the {@link JsonSerializer} to convert the elements + * @return the result {@link Array} containing all {@link JsonElement + * JsonElements} converted by the provided {@link JsonSerializer} + */ + public default T[] getAsArray(IntFunction generator, JsonSerializer serializer) { + return this.getAsList(serializer).toArray(generator); + } + /** * Gets the current element of the path. * diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java index 67ac20a1079..dfc423be402 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathActual.java @@ -1,36 +1,63 @@ package io.openems.common.jsonrpc.serialization; -import java.util.List; +import java.util.Objects; import java.util.function.Function; +import java.util.stream.Collector; import com.google.gson.JsonArray; -import com.google.gson.JsonElement; -import io.openems.common.exceptions.OpenemsRuntimeException; import io.openems.common.utils.JsonUtils; -public class JsonArrayPathActual implements JsonArrayPath { +public final class JsonArrayPathActual { - private final JsonArray object; + public static final class JsonArrayPathActualNonNull implements JsonArrayPath { - public JsonArrayPathActual(JsonElement object) { - if (!object.isJsonArray()) { - throw new OpenemsRuntimeException(object + " is not a JsonArray!"); + private final JsonArray element; + + public JsonArrayPathActualNonNull(JsonArray array) { + this.element = Objects.requireNonNull(array); + } + + @Override + public R collect(Collector collector) { + return JsonUtils.stream(this.element) // + .map(JsonElementPathActual.JsonElementPathActualNonNull::new) // + .collect(collector); + } + + @Override + public JsonArray get() { + return this.element; } - this.object = object.getAsJsonArray(); + } - @Override - public List getAsList(Function mapper) { - return JsonUtils.stream(this.object) // - .map(JsonElementPathActual::new) // - .map(mapper) // - .toList(); + public static final class JsonArrayPathActualNullable implements JsonArrayPathNullable { + + private final JsonArray element; + + public JsonArrayPathActualNullable(JsonArray array) { + this.element = array; + } + + @Override + public T mapIfPresent(Function mapper) { + return this.element == null ? null : mapper.apply(new JsonArrayPathActualNonNull(this.element)); + } + + @Override + public boolean isPresent() { + return this.element != null; + } + + @Override + public JsonArray getOrNull() { + return this.element; + } + } - @Override - public JsonArray get() { - return this.object; + private JsonArrayPathActual() { } } \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java index bc592b0eacf..061a9bb9622 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathDummy.java @@ -1,38 +1,78 @@ package io.openems.common.jsonrpc.serialization; -import static java.util.Collections.emptyList; - -import java.util.List; import java.util.function.Function; +import java.util.stream.Collector; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import io.openems.common.utils.JsonUtils; -public class JsonArrayPathDummy implements JsonArrayPath, JsonPathDummy { +public final class JsonArrayPathDummy { + + public static final class JsonArrayPathDummyNonNull implements JsonArrayPath, JsonPathDummy { + + private JsonPathDummy elementType; + + @Override + public R collect(Collector collector) { + final var supplier = collector.supplier().get(); + final var path = new JsonElementPathDummy.JsonElementPathDummyNonNull(); + collector.accumulator().accept(supplier, path); + this.elementType = path; + + return collector.finisher().apply(supplier); + } + + @Override + public JsonArray get() { + return new JsonArray(); + } - private JsonPathDummy elementType; + @Override + public JsonElement buildPath() { + return JsonUtils.buildJsonObject() // + .addProperty("type", "array") // + .addProperty("optional", false) // + .onlyIf(this.elementType != null, t -> t.add("elementType", this.elementType.buildPath())) // + .build(); + } - @Override - public List getAsList(Function mapper) { - final var path = new JsonElementPathDummy(); - mapper.apply(path); - this.elementType = path; - return emptyList(); } - @Override - public JsonArray get() { - return new JsonArray(); + public static final class JsonArrayPathDummyNullable implements JsonArrayPathNullable, JsonPathDummy { + + private JsonArrayPathDummyNonNull dummyPath; + + @Override + public T mapIfPresent(Function mapper) { + mapper.apply(this.dummyPath = new JsonArrayPathDummyNonNull()); + return null; + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public JsonArray getOrNull() { + return null; + } + + @Override + public JsonElement buildPath() { + return JsonUtils.buildJsonObject() // + .addProperty("type", "array") // + .addProperty("optional", true) // + .onlyIf(this.dummyPath != null && this.dummyPath.elementType != null, + t -> t.add("elementType", this.dummyPath.elementType.buildPath())) // + .build(); + } + } - @Override - public JsonElement buildPath() { - return JsonUtils.buildJsonObject() // - .addProperty("type", "array") // - .onlyIf(this.elementType != null, t -> t.add("elementType", this.elementType.buildPath())) // - .build(); + private JsonArrayPathDummy() { } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathNullable.java new file mode 100644 index 00000000000..92544ca0f0e --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonArrayPathNullable.java @@ -0,0 +1,124 @@ +package io.openems.common.jsonrpc.serialization; + +import java.lang.reflect.Array; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.function.Function; +import java.util.function.IntFunction; + +import com.google.gson.JsonArray; + +public interface JsonArrayPathNullable extends JsonPath { + + /** + * Maps the current value using the provided mapper if the current value is not + * null otherwise returns null. + * + * @param the type of the mapping result + * @param mapper the mapper to convert the non-null {@link JsonArrayPath} to a + * result object + * @return the result of the mapper function if the current value is not null + * otherwise null + */ + public T mapIfPresent(Function mapper); + + /** + * Maps the current value using the provided mapper to a {@link Optional}. + * + * @param the type of the mapping result + * @param mapper the mapper to convert the non-null {@link JsonArrayPath} to a + * result object + * @return the result of the mapper function of the current value in a + * {@link Optional} + */ + public default Optional mapToOptional(Function mapper) { + return Optional.ofNullable(this.mapIfPresent(mapper)); + } + + /** + * Maps the current value to a {@link List} using the provided mapper. + * + * @param the type of the mapping result + * @param mapper the mapper to convert the elements + * @return a {@link Optional} of the result {@link List} + */ + public default Optional> getAsOptionalList(Function mapper) { + return this.mapToOptional(t -> t.getAsList(mapper)); + } + + /** + * Maps the current value to a {@link List} using the provided mapper. + * + * @param the type of the mapping result + * @param serializer the {@link JsonSerializer} to convert elements + * @return a {@link Optional} of the result {@link List} + */ + public default Optional> getAsOptionalList(JsonSerializer serializer) { + return this.mapToOptional(t -> t.getAsList(serializer)); + } + + /** + * Maps the current value to a {@link Array} using the provided mapper. + * + * @param the type of the mapping result + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param mapper the mapper to convert the elements + * @return a {@link Optional} of the result {@link Array} + */ + public default Optional getAsOptionalArray(IntFunction generator, + Function mapper) { + return this.mapToOptional(t -> t.getAsArray(generator, mapper)); + } + + /** + * Maps the current value to a {@link Array} using the provided mapper. + * + * @param the type of the mapping result + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param serializer the {@link JsonSerializer} to convert elements + * @return a {@link Optional} of the result {@link Array} + */ + public default Optional getAsOptionalArray(IntFunction generator, JsonSerializer serializer) { + return this.mapToOptional(t -> t.getAsArray(generator, serializer)); + } + + /** + * Maps the current value to a {@link Set} using the provided mapper. + * + * @param the type of the mapping result + * @param mapper the mapper to convert the elements + * @return a {@link Optional} of the result {@link Set} + */ + public default Optional> getAsOptionalSet(Function mapper) { + return this.mapToOptional(t -> t.getAsSet(mapper)); + } + + /** + * Maps the current value to a {@link Set} using the provided mapper. + * + * @param the type of the mapping result + * @param serializer the {@link JsonSerializer} to convert elements + * @return a {@link Optional} of the result {@link Set} + */ + public default Optional> getAsOptionalSet(JsonSerializer serializer) { + return this.mapToOptional(t -> t.getAsSet(serializer)); + } + + /** + * Checks if the current value is present. + * + * @return true if the current value is present; else false + */ + public boolean isPresent(); + + /** + * Gets the current element of the path. + * + * @return the {@link JsonArray} + */ + public JsonArray getOrNull(); + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java index 043f8eb3779..c58b80e334c 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPath.java @@ -1,9 +1,34 @@ package io.openems.common.jsonrpc.serialization; +import static java.util.stream.Collectors.toMap; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Function; +import java.util.function.Predicate; + import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; public interface JsonElementPath extends JsonPath { + /** + * Gets the current element of the path. + * + * @return the {@link JsonElement} + */ + public JsonElement get(); + /** * Gets the current {@link JsonElementPath} as a {@link JsonObjectPath}. * @@ -18,12 +43,117 @@ public interface JsonElementPath extends JsonPath { */ public JsonArrayPath getAsJsonArrayPath(); + /** + * Gets the current {@link JsonElementPath} as a {@link JsonPrimitivePath}. + * + * @return the current element as a {@link JsonPrimitivePath} + */ + public JsonPrimitivePath getAsJsonPrimitivePath(); + /** * Gets the current {@link JsonElementPath} as a {@link StringPath}. * + * @param the actual type of the string value + * @param parser the parser to parse the string + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPath(StringParser parser) { + return this.getAsJsonPrimitivePath().getAsStringPath(parser); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing + * its raw string. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathString() { + return this.getAsJsonPrimitivePath().getAsStringPathString(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link ChannelAddress}. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathChannelAddress() { + return this.getAsJsonPrimitivePath().getAsStringPathChannelAddress(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link UUID}. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathUuid() { + return this.getAsJsonPrimitivePath().getAsStringPathUuid(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link SemanticVersion}. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathSemanticVersion() { + return this.getAsJsonPrimitivePath().getAsStringPathSemanticVersion(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link Enum}. + * + * @param the type of the {@link Enum} value + * @param enumClass the class type of the {@link Enum} + * @return the current element as a {@link StringPath} + */ + public default > StringPath getAsStringPathEnum(Class enumClass) { + return this.getAsJsonPrimitivePath().getAsStringPathEnum(enumClass); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link ZonedDateTime}. + * + * @param formatter the {@link DateTimeFormatter} to use to parse the string + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathZonedDateTime(DateTimeFormatter formatter) { + return this.getAsJsonPrimitivePath().getAsStringPathZonedDateTime(formatter); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link ZonedDateTime}. + * * @return the current element as a {@link StringPath} */ - public StringPath getAsStringPath(); + public default StringPath getAsStringPathZonedDateTime() { + return this.getAsJsonPrimitivePath().getAsStringPathZonedDateTime(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link LocalDate}. + * + * @param formatter the {@link DateTimeFormatter} to use to parse the string + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathLocalDate(DateTimeFormatter formatter) { + return this.getAsJsonPrimitivePath().getAsStringPathLocalDate(formatter); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link StringPath} containing a + * {@link LocalDate}. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathLocalDate() { + return this.getAsJsonPrimitivePath().getAsStringPathLocalDate(); + } /** * Gets the current {@link JsonElementPath} as a {@link String}. @@ -31,7 +161,185 @@ public interface JsonElementPath extends JsonPath { * @return the current element as a {@link String} */ public default String getAsString() { - return this.getAsStringPath().get(); + return this.getAsStringPathString().get(); + } + + /** + * Gets the current {@link JsonElementPath} as a parsed {@link String} with the + * {@link StringParser}. + * + * @param the actual type of the string value + * @param parser the parser to parse the string + * @return the current string parsed to the expected element + */ + public default T getAsStringParsed(StringParser parser) { + return this.getAsStringPath(parser).get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link ChannelAddress}. + * + * @return the current element as a {@link ChannelAddress} + */ + public default ChannelAddress getAsChannelAddress() { + return this.getAsStringPathChannelAddress().get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link UUID}. + * + * @return the current element as a {@link UUID} + */ + public default UUID getAsUuid() { + return this.getAsStringPathUuid().get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link SemanticVersion}. + * + * @return the current element as a {@link SemanticVersion} + */ + public default SemanticVersion getAsSemanticVersion() { + return this.getAsStringPathSemanticVersion().get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link Enum}. + * + * @param the type of the {@link Enum} + * @param enumClass the class type of the {@link Enum} + * @return the current element as a {@link Enum} + */ + public default > T getAsEnum(Class enumClass) { + return this.getAsStringPathEnum(enumClass).get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link ZonedDateTime}. + * + * @param formatter the {@link DateTimeFormatter} to use for parsing + * @return the current element as a {@link ZonedDateTime} + */ + public default ZonedDateTime getAsZonedDateTime(DateTimeFormatter formatter) { + return this.getAsStringPathZonedDateTime(formatter).get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link ZonedDateTime}. + * + * @return the current element as a {@link ZonedDateTime} + */ + public default ZonedDateTime getAsZonedDateTime() { + return this.getAsStringPathZonedDateTime().get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link LocalDate}. + * + * @param formatter the {@link DateTimeFormatter} to use for parsing + * @return the current element as a {@link LocalDate} + */ + public default LocalDate getAsLocalDate(DateTimeFormatter formatter) { + return this.getAsStringPathLocalDate(formatter).get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link LocalDate}. + * + * @return the current element as a {@link LocalDate} + */ + public default LocalDate getAsLocalDate() { + return this.getAsStringPathLocalDate().get(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link NumberPath}. + * + * @return the current element as a {@link NumberPath} + */ + public default NumberPath getAsNumberPath() { + return this.getAsJsonPrimitivePath().getAsNumberPath(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link Number}. + * + * @return the current element as a {@link Number} + */ + public default Number getAsNumber() { + return this.getAsNumberPath().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a double. + * + * @return the current element as a double + */ + public default double getAsDouble() { + return this.getAsNumberPath().getAsDouble(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a float. + * + * @return the current element as a float + */ + public default float getAsFloat() { + return this.getAsNumberPath().getAsFloat(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a long. + * + * @return the current element as a long + */ + public default long getAsLong() { + return this.getAsNumberPath().getAsLong(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a integer. + * + * @return the current element as a integer + */ + public default int getAsInt() { + return this.getAsNumberPath().getAsInt(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a short. + * + * @return the current element as a short + */ + public default short getAsShort() { + return this.getAsNumberPath().getAsShort(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a byte. + * + * @return the current element as a byte + */ + public default byte getAsByte() { + return this.getAsNumberPath().getAsByte(); + } + + /** + * Gets the current {@link JsonElementPath} as a {@link BooleanPath}. + * + * @return the current element as a {@link BooleanPath} + */ + public default BooleanPath getAsBooleanPath() { + return this.getAsJsonPrimitivePath().getAsBooleanPath(); + } + + /** + * Gets the current {@link JsonElementPath} as a primitive {@link Boolean}. + * + * @return the current element as a primitive {@link Boolean} + */ + public default boolean getAsBoolean() { + return this.getAsJsonPrimitivePath().getAsBoolean(); } /** @@ -41,8 +349,114 @@ public default String getAsString() { * @param the type of the final object * @param serializer the {@link JsonSerializer} to deserialize the * {@link JsonElement} to the object - * @return the current element as a {@link StringPath} + * @return the current element parsed with the provided {@link JsonSerializer} + */ + public default O getAsObject(JsonSerializer serializer) { + return serializer.deserializePath(this); + } + + public record Case(Predicate isApplicable, Function valueMapper) { + + } + + /** + * Experimental method to parse a object which can have different structures. e. + * g. a value is a sealed/abstract class and only the subtypes are set. + * + * @param the type of the result object + * @param cases the different cases a value can be and how it can be parsed + * @return the result object */ - public O getAsObject(JsonSerializer serializer); + public T multiple(List> cases); + + /** + * Checks if the current value is a {@link JsonPrimitive}. Do only use this + * method in combination with {@link #multiple(List)} inside the + * {@link Case#isApplicable} method. + * + * @return true if the current value is a {@link JsonPrimitive} + */ + public boolean isJsonPrimitive(); + + /** + * Checks if the current value is a {@link JsonObject}. Do only use this method + * in combination with {@link #multiple(List)} inside the + * {@link Case#isApplicable} method. + * + * @return true if the current value is a {@link JsonObject} + */ + public boolean isJsonObject(); + + /** + * Checks if the current value is a {@link Number}. Do only use this method in + * combination with {@link #multiple(List)} inside the {@link Case#isApplicable} + * method. + * + * @return true if the current value is a {@link Number} + */ + public boolean isNumber(); + + /** + * Serializes this object based on the provided subtypes. + * + * @param the type of the items to use for serializing each + * subtype + * @param the generic parent type + * @param items the items to use for serializing each subtype + * @param itemKeyMapper the mapper to map all types to a string + * @param objectToKeyPath the mapper to get the path to the json string + * identifier + * @param itemMapper the final mapper to map one json to an element + * @return the parsed element + */ + public default T polymorphic(// + List items, // + Function itemKeyMapper, // + Function> objectToKeyPath, // + BiFunction, T> itemMapper // + ) { + final var itemsByKey = items.stream().collect(toMap(itemKeyMapper, Function.identity())); + return this.polymorphic(itemsByKey, objectToKeyPath, itemMapper); + } + + /** + * Serializes this object based on the provided subtypes. + * + * @param the type of the items to use for serializing each + * subtype + * @param the generic parent type + * @param itemsByKey the items to use for serializing each subtype mapped + * by a string identifier + * @param objectToKeyPath the mapper to get the path to the json string + * identifier + * @param itemMapper the final mapper to map one json to an element + * @return the parsed element + */ + public T polymorphic(// + Map itemsByKey, // + Function> objectToKeyPath, // + BiFunction, T> itemMapper // + ); + + /** + * Serializes this object based on the provided {@link PolymorphicSerializer}. + * + * @param the type of the items to use for serializing + * each subtype + * @param the generic parent type + * @param polymorphicSerializer the {@link PolymorphicSerializer} to use for + * serializing + * @param objectToKeyPath the mapper to get the path to the json string + * identifier + * @return the parsed element + */ + public default T polymorphic(// + PolymorphicSerializer polymorphicSerializer, // + Function> objectToKeyPath // + ) { + return this.polymorphic(polymorphicSerializer.serializerByIdentifier(), objectToKeyPath, (t, u) -> { + return u.getValue().deserializePath(t); + }); + } } \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java index e0d955d1a60..13fbc928606 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathActual.java @@ -1,32 +1,133 @@ package io.openems.common.jsonrpc.serialization; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.BiFunction; +import java.util.function.Function; + import com.google.gson.JsonElement; -public class JsonElementPathActual implements JsonElementPath { - private final JsonElement element; +import io.openems.common.exceptions.OpenemsRuntimeException; +import io.openems.common.utils.JsonUtils; - public JsonElementPathActual(JsonElement element) { - this.element = element; - } +public final class JsonElementPathActual { - @Override - public JsonArrayPath getAsJsonArrayPath() { - return new JsonArrayPathActual(this.element); - } + public static final class JsonElementPathActualNonNull implements JsonElementPath { + + private final JsonElement element; + + public JsonElementPathActualNonNull(JsonElement element) { + this.element = Objects.requireNonNull(element); + } + + @Override + public JsonElement get() { + return this.element; + } + + @Override + public JsonArrayPath getAsJsonArrayPath() { + return new JsonArrayPathActual.JsonArrayPathActualNonNull(this.element.getAsJsonArray()); + } + + @Override + public JsonObjectPath getAsJsonObjectPath() { + return new JsonObjectPathActual.JsonObjectPathActualNonNull(this.element.getAsJsonObject()); + } + + @Override + public JsonPrimitivePath getAsJsonPrimitivePath() { + return new JsonPrimitivePathActual.JsonPrimitivePathActualNonNull(this.element.getAsJsonPrimitive()); + } + + @Override + public T multiple(List> cases) { + return cases.stream() // + .filter(t -> t.isApplicable().test(this)) // + .map(t -> t.valueMapper().apply(this)) // + .findAny().orElse(null); + } + + @Override + public T polymorphic(// + final Map itemsByKey, // + final Function> objectToKeyPath, // + final BiFunction, T> itemMapper // + ) { + final var stringIdentifier = objectToKeyPath.apply(this).get(); + final var mapper = itemsByKey.get(stringIdentifier); + + if (mapper == null) { + throw new OpenemsRuntimeException( + "No serializer defined for polymorphic type '" + stringIdentifier + "'."); + } + + return itemMapper.apply(this, Map.entry(stringIdentifier, mapper)); + } + + @Override + public boolean isJsonPrimitive() { + return this.element.isJsonPrimitive(); + } + + @Override + public boolean isJsonObject() { + return this.element.isJsonObject(); + } + + @Override + public boolean isNumber() { + return JsonUtils.isNumber(this.element); + } - @Override - public JsonObjectPath getAsJsonObjectPath() { - return new JsonObjectPathActual(this.element); } - @Override - public StringPath getAsStringPath() { - return new StringPathActual(this.element); + public static final class JsonElementPathActualNullable implements JsonElementPathNullable { + + private final JsonElement element; + + public JsonElementPathActualNullable(JsonElement element) { + this.element = element == null || element.isJsonNull() ? null : element; + } + + @Override + public T mapIfPresent(Function mapper) { + return this.element == null ? null : mapper.apply(new JsonElementPathActualNonNull(this.element)); + } + + @Override + public JsonObjectPathNullable getAsJsonObjectPathNullable() { + return new JsonObjectPathActual.JsonObjectPathActualNullable( + this.element == null ? null : this.element.getAsJsonObject()); + } + + @Override + public JsonArrayPathNullable getAsJsonArrayPathNullable() { + return new JsonArrayPathActual.JsonArrayPathActualNullable( + this.element == null ? null : this.element.getAsJsonArray()); + } + + @Override + public JsonPrimitivePathNullable getAsJsonPrimitivePathNullable() { + return new JsonPrimitivePathActual.JsonPrimitivePathActualNullable( + this.element == null ? null : this.element.getAsJsonPrimitive()); + } + + @Override + public boolean isPresent() { + return this.element != null; + } + + @Override + public JsonElement getOrNull() { + return this.element; + } + } - @Override - public O getAsObject(JsonSerializer deserializer) { - return deserializer.deserializePath(new JsonElementPathActual(this.element)); + private JsonElementPathActual() { } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java index 5d26b9bffb4..d44088d0634 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathDummy.java @@ -1,49 +1,170 @@ package io.openems.common.jsonrpc.serialization; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static java.util.stream.Collectors.toMap; + +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BiFunction; +import java.util.function.Function; + import com.google.gson.JsonElement; -import com.google.gson.JsonNull; +import com.google.gson.JsonPrimitive; -public class JsonElementPathDummy implements JsonElementPath, JsonPathDummy { +import io.openems.common.exceptions.OpenemsRuntimeException; +import io.openems.common.utils.JsonUtils; - private JsonPathDummy dummyPath; +public abstract sealed class JsonElementPathDummy implements JsonPathDummy { - @Override - public JsonArrayPath getAsJsonArrayPath() { - return this.withDummyPath(new JsonArrayPathDummy()); - } + private List dummyPaths; - @Override - public JsonObjectPath getAsJsonObjectPath() { - return this.withDummyPath(new JsonObjectPathDummy()); + @SuppressWarnings("unchecked") + protected T withDummyPath(T path) { + if (this.dummyPaths != null) { + if (this.dummyPaths.size() != 1 || !this.dummyPaths.get(0).getClass().equals(path.getClass())) { + throw new OpenemsRuntimeException("Path already set"); + } + return (T) this.dummyPaths.get(0); + } + this.dummyPaths = List.of(path); + return path; } - @Override - public StringPath getAsStringPath() { - return this.withDummyPath(new StringPathDummy()); + protected void withDummyPaths(List paths) { + if (this.dummyPaths != null) { + throw new OpenemsRuntimeException("Path already set"); + } + this.dummyPaths = paths; } @Override - public O getAsObject(JsonSerializer deserializer) { - final var dummyPath = new JsonElementPathDummy(); - this.withDummyPath(dummyPath); - return deserializer.deserializePath(dummyPath); + public JsonElement buildPath() { + if (this.dummyPaths == null) { + return JsonUtils.buildJsonObject() // + .addProperty("type", "element") // + .build(); + } + if (this.dummyPaths.size() == 1) { + return this.dummyPaths.get(0).buildPath(); + } + return JsonUtils.buildJsonObject() // + .addProperty("type", "multiple") // + .add("validTypes", this.dummyPaths.stream() // + .map(JsonPathDummy::buildPath) // + .collect(toJsonArray())) // + .build(); } - private T withDummyPath(T path) { - if (this.dummyPath != null) { - throw new RuntimeException("Path already set"); + public static final class JsonElementPathDummyNonNull extends JsonElementPathDummy + implements JsonElementPath, JsonPathDummy { + + @Override + public JsonElement get() { + return new JsonPrimitive(false); + } + + @Override + public JsonArrayPath getAsJsonArrayPath() { + return this.withDummyPath(new JsonArrayPathDummy.JsonArrayPathDummyNonNull()); + } + + @Override + public JsonObjectPath getAsJsonObjectPath() { + return this.withDummyPath(new JsonObjectPathDummy.JsonObjectPathDummyNonNull()); + } + + @Override + public JsonPrimitivePath getAsJsonPrimitivePath() { + return this.withDummyPath(new JsonPrimitivePathDummy.JsonPrimitivePathDummyNonNull()); + } + + @Override + public T multiple(List> cases) { + final var anyResult = new AtomicReference(); + this.withDummyPaths(cases.stream().map(c -> { + final var dummyPath = new JsonElementPathDummyNonNull(); + final var result = c.valueMapper().apply(dummyPath); + if (result != null) { + anyResult.set(result); + } + return dummyPath; + }).toList()); + return anyResult.get(); + } + + @Override + public boolean isJsonPrimitive() { + return false; + } + + @Override + public boolean isJsonObject() { + return false; + } + + @Override + public boolean isNumber() { + return false; + } + + @Override + public T polymorphic(// + Map itemsByKey, // + Function> objectToKeyPath, // + BiFunction, T> itemMapper // + ) { + final var paths = itemsByKey.entrySet().stream() // + .map(t -> { + final var path = new JsonElementPathDummy.JsonElementPathDummyNonNull(); + // TODO requires exact string t.getKey() + objectToKeyPath.apply(path); + itemMapper.apply(path, t); + return Map.entry(itemMapper.apply(path, t), path); + }) // + .collect(toMap(Entry::getKey, Entry::getValue)); + + this.withDummyPaths(paths.values().stream().toList()); + + return paths.keySet().stream().findFirst().get(); } - this.dummyPath = path; - return path; - } - public JsonPathDummy getDummyPath() { - return this.dummyPath; } - @Override - public JsonElement buildPath() { - return this.dummyPath == null ? JsonNull.INSTANCE : this.dummyPath.buildPath(); + public static final class JsonElementPathDummyNullable extends JsonElementPathDummy + implements JsonElementPathNullable, JsonPathDummy { + + @Override + public T mapIfPresent(Function mapper) { + return mapper.apply(this.withDummyPath(new JsonElementPathDummyNonNull())); + } + + @Override + public JsonObjectPathNullable getAsJsonObjectPathNullable() { + return this.withDummyPath(new JsonObjectPathDummy.JsonObjectPathDummyNullable()); + } + + @Override + public JsonArrayPathNullable getAsJsonArrayPathNullable() { + return this.withDummyPath(new JsonArrayPathDummy.JsonArrayPathDummyNullable()); + } + + @Override + public JsonPrimitivePathNullable getAsJsonPrimitivePathNullable() { + return this.withDummyPath(new JsonPrimitivePathDummy.JsonPrimitivePathDummyNullable()); + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public JsonElement getOrNull() { + return null; + } + } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathNullable.java new file mode 100644 index 00000000000..59462427726 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonElementPathNullable.java @@ -0,0 +1,207 @@ +package io.openems.common.jsonrpc.serialization; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; +import java.util.function.Function; + +import com.google.gson.JsonElement; + +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; + +public interface JsonElementPathNullable { + + /** + * Maps the current value using the provided mapper if the current value is not + * null otherwise returns null. + * + * @param the type of the mapping result + * @param mapper the mapper to convert the non-null {@link JsonArrayPath} to a + * result object + * @return the result of the mapper function if the current value is not null + * otherwise null + */ + public T mapIfPresent(Function mapper); + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link JsonArrayPathNullable}. + * + * @return the current element as a {@link JsonArrayPathNullable} + */ + public JsonArrayPathNullable getAsJsonArrayPathNullable(); + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link JsonObjectPathNullable}. + * + * @return the current element as a {@link JsonObjectPathNullable} + */ + public JsonObjectPathNullable getAsJsonObjectPathNullable(); + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link JsonPrimitivePathNullable}. + * + * @return the current element as a {@link JsonPrimitivePathNullable} + */ + public JsonPrimitivePathNullable getAsJsonPrimitivePathNullable(); + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link BooleanPathNullable}. + * + * @return the current element as a {@link BooleanPathNullable} + */ + public default BooleanPathNullable getAsBooleanPathNullable() { + return this.getAsJsonPrimitivePathNullable().getAsBooleanPathNullable(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link NumberPathNullable}. + * + * @return the current element as a {@link NumberPathNullable} + */ + public default NumberPathNullable getAsNumberPathNullable() { + return this.getAsJsonPrimitivePathNullable().getAsNumberPathNullable(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @param the actual type of the string value + * @param parser the parser to parse the string + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullable(StringParser parser) { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullable(parser); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableString() { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableString(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableChannelAddress() { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableChannelAddress(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @param the type of the enum value + * @param enumClass the type class of the enum + * @return the current element as a {@link StringPathNullable} + */ + public default > StringPathNullable getAsStringPathNullableEnum(Class enumClass) { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableEnum(enumClass); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableLocalDate() { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableLocalDate(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableLocalDate(DateTimeFormatter formatter) { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableLocalDate(formatter); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableSemanticVersion() { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableSemanticVersion(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableUuid() { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableUuid(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableZonedDateTime() { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableZonedDateTime(); + } + + /** + * Gets the current {@link JsonElementPathNullable} as a + * {@link StringPathNullable}. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableZonedDateTime(DateTimeFormatter formatter) { + return this.getAsJsonPrimitivePathNullable().getAsStringPathNullableZonedDateTime(formatter); + } + + /** + * Gets the current {@link JsonElementPath} as a Object serialized with the + * provided {@link JsonSerializer} or null if the current element is not + * present. + * + * @param the type of the final object + * @param serializer the {@link JsonSerializer} to deserialize the + * {@link JsonElement} to the object + * @return the current element parsed with the provided {@link JsonSerializer} + * or null if the current element is not present + */ + public default O getAsObjectOrNull(JsonSerializer serializer) { + return this.mapIfPresent(serializer::deserializePath); + } + + /** + * Checks if the current value is present. + * + * @return true if the current value is present; else false + */ + public boolean isPresent(); + + /** + * Gets the {@link JsonElement} value of the current path. + * + * @return the value; or null if not present + */ + public JsonElement getOrNull(); + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java index 5281a2fde79..00907275875 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPath.java @@ -1,58 +1,961 @@ package io.openems.common.jsonrpc.serialization; +import static java.util.stream.Collectors.mapping; + +import java.lang.reflect.Array; +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import java.util.function.Function; +import java.util.function.IntFunction; +import java.util.stream.Collector; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserString; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; + +public interface JsonObjectPath extends JsonPath { + + /** + * Gets the element associated with the member name from this object. + * + * @param member the name of the member + * @return the {@link JsonElementPath} of the member value + */ + public JsonElementPath getJsonElementPath(String member); + + /** + * Gets the element associated with the member name from this object as a + * {@link JsonElement}. + * + * @param member the name of the member + * @return the {@link JsonElement} of the member value + */ + public default JsonElement getJsonElement(String member) { + return this.getJsonElementPath(member).get(); + } + + /** + * Gets the primitive element associated with the member name from this object. + * + * @param member the name of the member + * @return the {@link JsonPrimitivePath} of the member value + */ + public default JsonPrimitivePath getJsonPrimitivePath(String member) { + return this.getJsonElementPath(member).getAsJsonPrimitivePath(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link JsonPrimitive}. + * + * @param member the name of the member + * @return the {@link JsonPrimitive} of the member value + */ + public default JsonPrimitive getJsonPrimitive(String member) { + return this.getJsonPrimitivePath(member).get(); + } + + /** + * Gets the nullable element associated with the member name from this object. + * + * @param member the name of the member + * @return the {@link JsonElementPathNullable} of the member value + */ + public JsonElementPathNullable getNullableJsonElementPath(String member); + + /** + * Gets the null-able primitive associated with the member name from this + * object. + * + * @param member the name of the member + * @return the {@link JsonPrimitivePathNullable} of the member value + */ + public default JsonPrimitivePathNullable getNullableJsonPrimitivePath(String member) { + return this.getNullableJsonElementPath(member).getAsJsonPrimitivePathNullable(); + } + + /** + * Collects the current object entries into a result object with the provided + * {@link Collector}. + * + * @param the type of the {@link StringPath} + * @param the mutable accumulation type of the reduction operation + * (often hidden as an implementation detail) + * @param the result type of the reduction operation + * @param keyParser the key string parser + * @param collector the {@link Collector} to defined the reduction operations + * @return the result of the reduction + */ + public R collect(// + StringParser keyParser, // + Collector, JsonElementPath>, A, R> collector // + ); + + /** + * Collects the current object entries into a result object with the provided + * {@link Collector}. + * + * @param the mutable accumulation type of the reduction operation + * (often hidden as an implementation detail) + * @param the result type of the reduction operation + * @param collector the {@link Collector} to defined the reduction operations + * @return the result of the reduction + */ + public default R collectStringKeys(Collector, A, R> collector) { + return this.collect(new StringParserString(), + mapping(t -> Map.entry(t.getKey().get(), t.getValue()), collector)); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath}. + * + * @param member the name of the member + * @return the {@link StringPath} of the member value + */ + public default StringPath getStringPath(String member) { + return this.getJsonElementPath(member).getAsStringPathString(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath}. + * + * @param the type of the result string path + * @param member the name of the member + * @param parser the parser to use to parse the string value + * @return the {@link StringPath} of the member value + */ + public default StringPath getStringPath(String member, StringParser parser) { + return this.getJsonElementPath(member).getAsStringPath(parser); + } + + /** + * Gets the element associate with the member name from this object as a string + * parsed to the result object. + * + * @param the type of the result object + * @param member the name of the member + * @param parser the {@link StringParser} to parse the raw string value + * @return the parsed string + */ + public default T getStringParsed(String member, StringParser parser) { + return this.getJsonElementPath(member).getAsStringPath(parser).get(); + } + + /** + * Gets the element associate with the member name from this object as a string + * parsed to the result object or null if not present. + * + * @param the type of the result object + * @param member the name of the member + * @param parser the {@link StringParser} to parse the raw string value + * @return the parsed string or null if not present + */ + public default T getStringParsedOrNull(String member, StringParser parser) { + return this.getNullableJsonPrimitivePath(member).getAsStringParsedOrNull(parser); + } + + /** + * Gets the element associate with the member name from this object as a + * {@link Optional} of the string parsed to the result object or empty if not + * present. + * + * @param the type of the result object + * @param member the name of the member + * @param parser the {@link StringParser} to parse the raw string value + * @return the {@link Optional} of the parsed string + */ + public default Optional getOptionalStringParsed(String member, StringParser parser) { + return this.getNullableJsonPrimitivePath(member).getAsOptionalStringParsed(parser); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathString(String member) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableString(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathChannelAddress(String member) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableChannelAddress(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param the type of the enum value + * @param member the name of the member + * @param enumClass the type class of the enum + * @return the {@link StringPathNullable} of the member value + */ + public default > StringPathNullable getNullableStringPathEnum(// + String member, // + Class enumClass // + ) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableEnum(enumClass); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathLocalDate(String member) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableLocalDate(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathLocalDate(String member, + DateTimeFormatter formatter) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableLocalDate(formatter); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathSemanticVersion(String member) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableSemanticVersion(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param the actual type of the string value + * @param member the name of the member + * @param parser the parser to parse the string + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPath(String member, StringParser parser) { + return this.getNullableJsonElementPath(member).getAsStringPathNullable(parser); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathUuid(String member) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableUuid(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathZonedDateTime(String member) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableZonedDateTime(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPathNullable}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the {@link StringPathNullable} of the member value + */ + public default StringPathNullable getNullableStringPathZonedDateTime(String member, + DateTimeFormatter formatter) { + return this.getNullableJsonElementPath(member).getAsStringPathNullableZonedDateTime(formatter); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link UUID}. + * + * @param member the name of the member + * @return the {@link StringPath} of type {@link UUID} of the member value + */ + public default StringPath getStringPathUuid(String member) { + return this.getJsonElementPath(member).getAsStringPathUuid(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link SemanticVersion}. + * + * @param member the name of the member + * @return the {@link StringPath} of type {@link SemanticVersion} of the member + * value + */ + public default StringPath getStringPathSemanticVersion(String member) { + return this.getJsonElementPath(member).getAsStringPathSemanticVersion(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link Enum}. + * + * @param the type of the {@link Enum} value + * @param member the name of the member + * @param enumClass the class type of the {@link Enum} + * @return the {@link StringPath} of type {@link Enum} of the member value + */ + public default > StringPath getStringPathEnum(String member, Class enumClass) { + return this.getJsonElementPath(member).getAsStringPathEnum(enumClass); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link ZonedDateTime}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} to use to parse the string + * @return the {@link StringPath} of type {@link ZonedDateTime} of the member + * value + */ + public default StringPath getStringPathZonedDateTime(String member, DateTimeFormatter formatter) { + return this.getJsonElementPath(member).getAsStringPathZonedDateTime(formatter); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link ZonedDateTime}. + * + * @param member the name of the member + * @return the {@link StringPath} of type {@link ZonedDateTime} of the member + * value + */ + public default StringPath getStringPathZonedDateTime(String member) { + return this.getJsonElementPath(member).getAsStringPathZonedDateTime(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link LocalDate}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} to use to parse the string + * @return the {@link StringPath} of type {@link LocalDate} of the member value + */ + public default StringPath getStringPathLocalDate(String member, DateTimeFormatter formatter) { + return this.getJsonElementPath(member).getAsStringPathLocalDate(formatter); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link StringPath} of type {@link LocalDate}. + * + * @param member the name of the member + * @return the {@link StringPath} of type {@link LocalDate} of the member value + */ + public default StringPath getStringPathLocalDate(String member) { + return this.getJsonElementPath(member).getAsStringPathLocalDate(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link NumberPath}. + * + * @param member the name of the member + * @return the {@link NumberPath} of the member value + */ + public default NumberPath getNumberPath(String member) { + return this.getJsonElementPath(member).getAsNumberPath(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link NumberPathNullable}. + * + * @param member the name of the member + * @return the {@link NumberPathNullable} of the member value + */ + public default NumberPathNullable getNullableNumberPath(String member) { + return this.getNullableJsonElementPath(member).getAsNumberPathNullable(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link String}. + * + * @param member the name of the member + * @return the {@link String} of the member value + */ + public default String getString(String member) { + return this.getJsonElementPath(member).getAsString(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link String} or null if not present. + * + * @param member the name of the member + * @return the {@link String} of the member value or null if not present + */ + public default String getStringOrNull(String member) { + return this.getNullableStringPathString(member).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link String}. + * + * @param member the name of the member + * @return the {@link Optional} of type {@link String} of the member value + */ + public default Optional getOptionalString(String member) { + return this.getNullableStringPathString(member).getOptional(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link UUID}. + * + * @param member the name of the member + * @return the {@link UUID} of the member value + */ + public default UUID getUuid(String member) { + return this.getStringPathUuid(member).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link UUID} or null if not present. + * + * @param member the name of the member + * @return the {@link UUID} of the member value or null if not present + */ + public default UUID getUuidOrNull(String member) { + return this.getNullableStringPathUuid(member).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link UUID}. + * + * @param member the name of the member + * @return the {@link Optional} of type {@link UUID} of the member value + */ + public default Optional getOptionalUuid(String member) { + return this.getNullableStringPathUuid(member).getOptional(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link SemanticVersion}. + * + * @param member the name of the member + * @return the {@link SemanticVersion} of the member value + */ + public default SemanticVersion getSemanticVersion(String member) { + return this.getStringPathSemanticVersion(member).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link SemanticVersion} or null if not present. + * + * @param member the name of the member + * @return the {@link SemanticVersion} of the member value or null if not + * present + */ + public default SemanticVersion getSemanticVersionOrNull(String member) { + return this.getNullableStringPathSemanticVersion(member).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link SemanticVersion}. + * + * @param member the name of the member + * @return the {@link Optional} of type {@link SemanticVersion} of the member + * value + */ + public default Optional getOptionalSemanticVersion(String member) { + return this.getNullableStringPathSemanticVersion(member).getOptional(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Enum}. + * + * @param the type of the {@link Enum} value + * @param member the name of the member + * @param enumClass the class type of the {@link Enum} + * @return the {@link Enum} of the member value + */ + public default > T getEnum(String member, Class enumClass) { + return this.getStringPathEnum(member, enumClass).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Enum} or null if not present. + * + * @param the type of the enum value + * @param member the name of the member + * @param enumClass the type class of the enum + * @return the {@link Enum} of the member value or null if not present + */ + public default > T getEnumOrNull(String member, Class enumClass) { + return this.getNullableStringPathEnum(member, enumClass).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link Enum}. + * + * @param the type of the enum value + * @param member the name of the member + * @param enumClass the type class of the enum + * @return the {@link Optional} of type {@link Enum} of the member value + */ + public default > Optional getOptionalEnum(String member, Class enumClass) { + return this.getNullableStringPathEnum(member, enumClass).getOptional(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link ZonedDateTime}. + * + * @param member the name of the member + * @return the {@link ZonedDateTime} of the member value + */ + public default ZonedDateTime getZonedDateTime(String member) { + return this.getStringPathZonedDateTime(member).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link ZonedDateTime}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} to use to parse the string + * @return the {@link ZonedDateTime} of the member value + */ + public default ZonedDateTime getZonedDateTime(String member, DateTimeFormatter formatter) { + return this.getStringPathZonedDateTime(member, formatter).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link ZonedDateTime} or null if not present. + * + * @param member the name of the member + * @return the {@link ZonedDateTime} of the member value or null if not present + */ + public default ZonedDateTime getZonedDateTimeOrNull(String member) { + return this.getNullableStringPathZonedDateTime(member).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link ZonedDateTime} or null if not present. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the {@link ZonedDateTime} of the member value or null if not present + */ + public default ZonedDateTime getZonedDateTimeOrNull(String member, DateTimeFormatter formatter) { + return this.getNullableStringPathZonedDateTime(member, formatter).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link String}. + * + * @param member the name of the member + * @return the {@link Optional} of type {@link String} of the member value + */ + public default Optional getOptionalZonedDateTime(String member) { + return this.getNullableStringPathZonedDateTime(member).getOptional(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link String}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the {@link Optional} of type {@link String} of the member value + */ + public default Optional getOptionalZonedDateTime(String member, DateTimeFormatter formatter) { + return this.getNullableStringPathZonedDateTime(member, formatter).getOptional(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link LocalDate}. + * + * @param member the name of the member + * @return the {@link LocalDate} of the member value + */ + public default LocalDate getLocalDate(String member) { + return this.getStringPathLocalDate(member).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link LocalDate}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} to use to parse the string + * @return the {@link LocalDate} of the member value + */ + public default LocalDate getLocalDate(String member, DateTimeFormatter formatter) { + return this.getStringPathLocalDate(member, formatter).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link LocalDate} or null if not present. + * + * @param member the name of the member + * @return the {@link LocalDate} of the member value or null if not present + */ + public default LocalDate getLocalDateOrNull(String member) { + return this.getNullableStringPathLocalDate(member).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link LocalDate} or null if not present. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the {@link LocalDate} of the member value or null if not present + */ + public default LocalDate getLocalDateOrNull(String member, DateTimeFormatter formatter) { + return this.getNullableStringPathLocalDate(member, formatter).getOrNull(); + } -import com.google.gson.JsonArray; -import com.google.gson.JsonObject; + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link LocalDate}. + * + * @param member the name of the member + * @return the {@link Optional} of type {@link LocalDate} of the member value + */ + public default Optional getOptionalLocalDate(String member) { + return this.getNullableStringPathLocalDate(member).getOptional(); + } -public interface JsonObjectPath extends JsonPath { + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link LocalDate}. + * + * @param member the name of the member + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the {@link Optional} of type {@link LocalDate} of the member value + */ + public default Optional getOptionalLocalDate(String member, DateTimeFormatter formatter) { + return this.getNullableStringPathLocalDate(member, formatter).getOptional(); + } /** - * Gets the element associated with the member name from this object. + * Gets the element associated with the member name from this object as a + * primitive {@link Double}. * * @param member the name of the member - * @return the {@link JsonElementPath} of the member value + * @return the primitive {@link Double} of the member value */ - public JsonElementPath getJsonElementPath(String member); + public default double getDouble(String member) { + return this.getNumberPath(member).getAsDouble(); + } + + /** + * Gets the element associated with the member name as a double or returns the + * provided default value if the current element is not present. + * + * @param member the name of the member + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the primitive {@link Double} of the member value or the default value + * if absent + */ + public default double getDoubleOrDefault(String member, double defaultValue) { + return this.getNullableNumberPath(member).getAsDoubleOrDefault(defaultValue); + } /** * Gets the element associated with the member name from this object as a - * {@link StringPath}. + * {@link Optional} of double. * * @param member the name of the member - * @return the {@link StringPath} of the member value + * @return the {@link Optional} of the member value */ - public default StringPath getStringPath(String member) { - return this.getJsonElementPath(member).getAsStringPath(); + public default Optional getOptionalDouble(String member) { + return this.getNullableNumberPath(member).getAsOptionalDouble(); } /** - * Gets the enum value of the element associated with the member name of this - * object. + * Gets the element associated with the member name from this object as a + * primitive {@link Float}. * - * @param the type of the enum - * @param member the name of the member - * @param enumClass the class of the enum - * @return the enum value + * @param member the name of the member + * @return the primitive {@link Float} of the member value */ - public default > T getEnum(String member, Class enumClass) { - try { - return Enum.valueOf(enumClass, this.getString(member)); - } catch (Exception e) { - return enumClass.getEnumConstants()[0]; - } + public default float getFloat(String member) { + return this.getNumberPath(member).getAsFloat(); + } + + /** + * Gets the element associated with the member name as a float or returns the + * provided default value if the current element is not present. + * + * @param member the name of the member + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the primitive {@link Float} of the member value or the default value + * if absent + */ + public default float getFloatOrDefault(String member, float defaultValue) { + return this.getNullableNumberPath(member).getAsFloatOrDefault(defaultValue); } /** * Gets the element associated with the member name from this object as a - * {@link String}. + * {@link Optional} of float. * * @param member the name of the member - * @return the {@link String} of the member value + * @return the {@link Optional} of the member value */ - public default String getString(String member) { - return this.getStringPath(member).get(); + public default Optional getOptionalFloat(String member) { + return this.getNullableNumberPath(member).getAsOptionalFloat(); + } + + /** + * Gets the element associated with the member name from this object as a + * primitive {@link Long}. + * + * @param member the name of the member + * @return the primitive {@link Long} of the member value + */ + public default long getLong(String member) { + return this.getNumberPath(member).getAsLong(); + } + + /** + * Gets the element associated with the member name as a long or returns the + * provided default value if the current element is not present. + * + * @param member the name of the member + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the primitive {@link Long} of the member value or the default value + * if absent + */ + public default long getLongOrDefault(String member, long defaultValue) { + return this.getNullableNumberPath(member).getAsLongOrDefault(defaultValue); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of long. + * + * @param member the name of the member + * @return the {@link Optional} of the member value + */ + public default Optional getOptionalLong(String member) { + return this.getNullableNumberPath(member).getAsOptionalLong(); + } + + /** + * Gets the element associated with the member name from this object as a + * primitive {@link Integer}. + * + * @param member the name of the member + * @return the primitive {@link Integer} of the member value + */ + public default int getInt(String member) { + return this.getNumberPath(member).getAsInt(); + } + + /** + * Gets the element associated with the member name as a integer or returns the + * provided default value if the current element is not present. + * + * @param member the name of the member + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the primitive {@link Integer} of the member value or the default + * value if absent + */ + public default int getIntOrDefault(String member, int defaultValue) { + return this.getNullableNumberPath(member).getAsIntOrDefault(defaultValue); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of integer. + * + * @param member the name of the member + * @return the {@link Optional} of the member value + */ + public default Optional getOptionalInt(String member) { + return this.getNullableNumberPath(member).getAsOptionalInt(); + } + + /** + * Gets the element associated with the member name from this object as a + * primitive {@link Short}. + * + * @param member the name of the member + * @return the primitive {@link Short} of the member value + */ + public default short getShort(String member) { + return this.getNumberPath(member).getAsShort(); + } + + /** + * Gets the element associated with the member name as a short or returns the + * provided default value if the current element is not present. + * + * @param member the name of the member + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the primitive {@link Short} of the member value or the default value + * if absent + */ + public default short getShortOrDefault(String member, short defaultValue) { + return this.getNullableNumberPath(member).getAsShortOrDefault(defaultValue); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of short. + * + * @param member the name of the member + * @return the {@link Optional} of the member value + */ + public default Optional getOptionalShort(String member) { + return this.getNullableNumberPath(member).getAsOptionalShort(); + } + + /** + * Gets the element associated with the member name from this object as a + * primitive {@link Byte}. + * + * @param member the name of the member + * @return the primitive {@link Byte} of the member value + */ + public default byte getByte(String member) { + return this.getNumberPath(member).getAsByte(); + } + + /** + * Gets the element associated with the member name as a byte or returns the + * provided default value if the current element is not present. + * + * @param member the name of the member + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the primitive {@link Byte} of the member value or the default value + * if absent + */ + public default byte getByteOrDefault(String member, byte defaultValue) { + return this.getNullableNumberPath(member).getAsByteOrDefault(defaultValue); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of byte. + * + * @param member the name of the member + * @return the {@link Optional} of the member value + */ + public default Optional getOptionalByte(String member) { + return this.getNullableNumberPath(member).getAsOptionalByte(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link BooleanPath}. + * + * @param member the name of the member + * @return the {@link BooleanPath} of the member value + */ + public default BooleanPath getBooleanPath(String member) { + return this.getJsonElementPath(member).getAsBooleanPath(); + } + + /** + * Gets the element associated with the member name from this object as a + * primitive {@link Boolean}. + * + * @param member the name of the member + * @return the primitive {@link Boolean} of the member value + */ + public default boolean getBoolean(String member) { + return this.getBooleanPath(member).get(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link BooleanPathNullable}. + * + * @param member the name of the member + * @return the {@link BooleanPathNullable} of the member value + */ + public default BooleanPathNullable getBooleanPathNullable(String member) { + return this.getNullableJsonElementPath(member).getAsBooleanPathNullable(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Boolean}. + * + * @param member the name of the member + * @return the {@link Boolean} of the member value or null if not present + */ + public default Boolean getBooleanNullable(String member) { + return this.getBooleanPathNullable(member).getOrNull(); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of type {@link Boolean}. + * + * @param member the name of the member + * @return the {@link Optional} of type {@link Boolean} of the member value + */ + public default Optional getOptionalBoolean(String member) { + return this.getBooleanPathNullable(member).getOptional(); } /** @@ -88,6 +991,17 @@ public default JsonArrayPath getJsonArrayPath(String member) { return this.getJsonElementPath(member).getAsJsonArrayPath(); } + /** + * Gets the nullable array element associated with the member name from this + * object. + * + * @param member the name of the member + * @return the {@link JsonArrayPathNullable} of the member value + */ + public default JsonArrayPathNullable getNullableJsonArrayPath(String member) { + return this.getNullableJsonElementPath(member).getAsJsonArrayPathNullable(); + } + /** * Gets the element associated with the member name from this object as a * {@link JsonArray}. @@ -99,6 +1013,17 @@ public default JsonArray getJsonArray(String member) { return this.getJsonArrayPath(member).get(); } + /** + * Gets the element associated with the member name from this object as a + * {@link JsonArray} or null if not present. + * + * @param member the name of the member + * @return the {@link JsonArray} of the member value or null if not present + */ + public default JsonArray getJsonArrayOrNull(String member) { + return this.getNullableJsonArrayPath(member).getOrNull(); + } + /** * Gets the element associated with the member name from this object as a * {@link List}. @@ -125,6 +1050,153 @@ public default List getList(String member, JsonSerializer serializer) return this.getJsonArrayPath(member).getAsList(serializer); } + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of {@link List}. + * + * @param the type of the elements in the list + * @param member the name of the member + * @param mapper the mapper to deserialize the elements + * @return the {@link Optional} of {@link List} of the member value or + * {@link Optional#empty()} if not present + */ + public default Optional> getOptionalList(String member, Function mapper) { + return this.getNullableJsonArrayPath(member).getAsOptionalList(mapper); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of {@link List}. + * + * @param the type of the elements in the list + * @param member the name of the member + * @param serializer the {@link JsonSerializer} to deserialize the elements + * @return the {@link Optional} of {@link List} of the member value or + * {@link Optional#empty()} if not present + */ + public default Optional> getOptionalList(String member, JsonSerializer serializer) { + return this.getNullableJsonArrayPath(member).getAsOptionalList(serializer); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Array}. + * + * @param the type of the elements in the array + * @param member the name of the member + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param mapper the mapper to deserialize the elements + * @return the {@link Array} of the member value + */ + public default T[] getArray(String member, IntFunction generator, Function mapper) { + return this.getJsonArrayPath(member).getAsArray(generator, mapper); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Array}. + * + * @param the type of the elements in the array + * @param member the name of the member + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param serializer the {@link JsonSerializer} to deserialize the elements + * @return the {@link Array} of the member value + */ + public default T[] getArray(String member, IntFunction generator, JsonSerializer serializer) { + return this.getJsonArrayPath(member).getAsArray(generator, serializer); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of {@link Array}. + * + * @param the type of the elements in the array + * @param member the name of the member + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param mapper the mapper to deserialize the elements + * @return the {@link Optional} of {@link Array} of the member value or + * {@link Optional#empty()} if not present + */ + public default Optional getOptionalArray(String member, IntFunction generator, + Function mapper) { + return this.getNullableJsonArrayPath(member).getAsOptionalArray(generator, mapper); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of {@link Array}. + * + * @param the type of the elements in the array + * @param member the name of the member + * @param generator a function which produces a new array of the desired type + * and the provided length + * @param serializer the {@link JsonSerializer} to deserialize the elements + * @return the {@link Optional} of {@link Array} of the member value or + * {@link Optional#empty()} if not present + */ + public default Optional getOptionalArray(String member, IntFunction generator, + JsonSerializer serializer) { + return this.getNullableJsonArrayPath(member).getAsOptionalArray(generator, serializer); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Set}. + * + * @param the type of the elements + * @param member the name of the member + * @param mapper the mapper to convert the elements + * @return the result {@link Set} containing all {@link JsonElement + * JsonElements} converted by the provided mapper + */ + public default Set getSet(String member, Function mapper) { + return this.getJsonArrayPath(member).getAsSet(mapper); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Set}. + * + * @param the type of the elements + * @param member the name of the member + * @param serializer the {@link JsonSerializer} to deserialize the elements + * @return the {@link Set} of the member value + */ + public default Set getSet(String member, JsonSerializer serializer) { + return this.getJsonArrayPath(member).getAsSet(serializer::deserializePath); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of {@link Set}. + * + * @param the type of the elements in the set + * @param member the name of the member + * @param mapper the mapper to deserialize the elements + * @return the {@link Optional} of {@link Set} of the member value or + * {@link Optional#empty()} if not present + */ + public default Optional> getOptionalSet(String member, Function mapper) { + return this.getNullableJsonArrayPath(member).getAsOptionalSet(mapper); + } + + /** + * Gets the element associated with the member name from this object as a + * {@link Optional} of {@link Set}. + * + * @param the type of the elements in the set + * @param member the name of the member + * @param serializer the {@link JsonSerializer} to deserialize the elements + * @return the {@link Optional} of {@link Set} of the member value or + * {@link Optional#empty()} if not present + */ + public default Optional> getOptionalSet(String member, JsonSerializer serializer) { + return this.getNullableJsonArrayPath(member).getAsOptionalSet(serializer); + } + /** * Gets the element associated with the member name from this object as the * generic object. @@ -134,10 +1206,23 @@ public default List getList(String member, JsonSerializer serializer) * @param serializer the {@link JsonSerializer} to deserialize the element * @return the object of the member value */ - public default T getElement(String member, JsonSerializer serializer) { + public default T getObject(String member, JsonSerializer serializer) { return this.getJsonElementPath(member).getAsObject(serializer); } + /** + * Gets the element associated with the member name from this object as the + * generic object or null if not present. + * + * @param the type of the element + * @param member the name of the member + * @param serializer the {@link JsonSerializer} to deserialize the element + * @return the object of the member value or null if the member is not present + */ + public default T getObjectOrNull(String member, JsonSerializer serializer) { + return this.getNullableJsonElementPath(member).getAsObjectOrNull(serializer); + } + /** * Gets the current element of the path. * diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java index 7de86237260..8139cd78136 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathActual.java @@ -1,33 +1,82 @@ package io.openems.common.jsonrpc.serialization; -import com.google.gson.JsonElement; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Objects; +import java.util.function.Function; +import java.util.stream.Collector; + import com.google.gson.JsonObject; -import io.openems.common.exceptions.OpenemsRuntimeException; +public final class JsonObjectPathActual { + + public static class JsonObjectPathActualNonNull implements JsonObjectPath { -public class JsonObjectPathActual implements JsonObjectPath { - private final JsonObject object; + private final JsonObject element; - public JsonObjectPathActual(JsonElement object) { - if (!object.isJsonObject()) { - throw new OpenemsRuntimeException(object + " is not a JsonObject!"); + public JsonObjectPathActualNonNull(JsonObject object) { + this.element = Objects.requireNonNull(object); + } + + @Override + public JsonElementPath getJsonElementPath(String member) { + return new JsonElementPathActual.JsonElementPathActualNonNull(Objects.requireNonNull(this.get().get(member), + "Member [" + member + "] was not part of " + this.element)); + } + + @Override + public JsonElementPathNullable getNullableJsonElementPath(String member) { + return new JsonElementPathActual.JsonElementPathActualNullable(this.get().get(member)); + } + + @Override + public R collect(// + StringParser keyParser, // + Collector, JsonElementPath>, A, R> collector // + ) { + return this.element.entrySet().stream() // + .map(t -> Map., JsonElementPath>entry( + new StringPathActual.StringPathActualNonNull(t.getKey(), keyParser::parse), + new JsonElementPathActual.JsonElementPathActualNonNull(t.getValue()))) // + .collect(collector); + } + + @Override + public JsonObject get() { + return this.element; } - this.object = object.getAsJsonObject(); - } - @Override - public JsonElementPath getJsonElementPath(String member) { - return new JsonElementPathActual(this.object.get(member)); } - @Override - public JsonObjectPath getJsonObjectPath(String member) { - return new JsonObjectPathActual(this.object.get(member)); + public static class JsonObjectPathActualNullable implements JsonObjectPathNullable { + + private final JsonObject element; + + public JsonObjectPathActualNullable(JsonObject object) { + this.element = object; + } + + @Override + public T mapIfPresent(Function mapping) { + if (this.element == null) { + return null; + } + return mapping.apply(new JsonObjectPathActualNonNull(this.element)); + } + + @Override + public boolean isPresent() { + return this.element != null; + } + + @Override + public JsonObject getOrNull() { + return this.element; + } + } - @Override - public JsonObject get() { - return this.object; + private JsonObjectPathActual() { } } \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java index 68639fee6b8..76374e4c454 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathDummy.java @@ -5,43 +5,102 @@ import java.util.Map; import java.util.Map.Entry; import java.util.TreeMap; +import java.util.function.Function; +import java.util.stream.Collector; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import io.openems.common.utils.JsonUtils; -public class JsonObjectPathDummy implements JsonObjectPath, JsonPathDummy { +public final class JsonObjectPathDummy { - private final Map paths = new TreeMap<>(); + public static final class JsonObjectPathDummyNonNull implements JsonObjectPath, JsonPathDummy { - @Override - public JsonElementPath getJsonElementPath(String member) { - return this.withDummyPath(member, new JsonElementPathDummy()); - } + private final Map paths = new TreeMap<>(); - @Override - public JsonObjectPath getJsonObjectPath(String member) { - return this.withDummyPath(member, new JsonObjectPathDummy()); - } + @Override + public JsonElementPath getJsonElementPath(String member) { + return this.withDummyPath(member, new JsonElementPathDummy.JsonElementPathDummyNonNull()); + } + + @Override + public JsonElementPathNullable getNullableJsonElementPath(String member) { + return this.withDummyPath(member, new JsonElementPathDummy.JsonElementPathDummyNullable()); + } + + @Override + public R collect(// + StringParser keyParser, // + Collector, JsonElementPath>, A, R> collector // + ) { + final var resultContainer = collector.supplier().get(); + final var dummyPath = new JsonElementPathDummy.JsonElementPathDummyNonNull(); + final var example = keyParser.getExample(); + collector.accumulator().accept(resultContainer, + Map.entry(new StringPathDummy.StringPathDummyNonNull<>(example.raw(), example.value()), dummyPath)); + // TODO should be special case + this.withDummyPath(example.raw(), dummyPath); + return collector.finisher().apply(resultContainer); + } + + @Override + public JsonObject get() { + return new JsonObject(); + } + + @Override + public JsonElement buildPath() { + return JsonUtils.buildJsonObject() // + .addProperty("type", "object") // + .addProperty("optional", false) // + .add("properties", this.paths.entrySet().stream() // + .collect(toJsonObject(Entry::getKey, input -> input.getValue().buildPath()))) // + .build(); + } + + private final T withDummyPath(String member, T path) { + this.paths.put(member, path); + return path; + } - @Override - public JsonObject get() { - return new JsonObject(); } - @Override - public JsonElement buildPath() { - return JsonUtils.buildJsonObject() // - .addProperty("type", "object") // - .add("properties", this.paths.entrySet().stream() // - .collect(toJsonObject(Entry::getKey, input -> input.getValue().buildPath()))) // - .build(); + public static final class JsonObjectPathDummyNullable implements JsonObjectPathNullable, JsonPathDummy { + + private JsonObjectPathDummyNonNull dummyNonNullPath; + + @Override + public T mapIfPresent(Function mapping) { + final var path = new JsonObjectPathDummyNonNull(); + mapping.apply(path); + return null; + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public JsonObject getOrNull() { + return null; + } + + @Override + public JsonElement buildPath() { + return JsonUtils.buildJsonObject() // + .addProperty("type", "object") // + .addProperty("optional", true) // + .add("properties", this.dummyNonNullPath == null ? new JsonObject() + : this.dummyNonNullPath.paths.entrySet().stream() // + .collect(toJsonObject(Entry::getKey, input -> input.getValue().buildPath()))) // + .build(); + } + } - private final T withDummyPath(String member, T path) { - this.paths.put(member, path); - return path; + private JsonObjectPathDummy() { } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathNullable.java new file mode 100644 index 00000000000..ab9b24aa6fd --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonObjectPathNullable.java @@ -0,0 +1,33 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.function.Function; + +import com.google.gson.JsonObject; + +public interface JsonObjectPathNullable extends JsonPath { + + /** + * Maps the current value if present with the provided mapping function other + * returns null. + * + * @param the type of the result object + * @param mapper the mapping function + * @return the result object or null if the current value is not present + */ + public T mapIfPresent(Function mapper); + + /** + * Checks if the current value is present. + * + * @return true if the current value is present; else false + */ + public boolean isPresent(); + + /** + * Gets the boolean value of the current path. + * + * @return the value; or null if not present + */ + public JsonObject getOrNull(); + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java index 4c9b0d82a59..27073a603ea 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPath.java @@ -1,5 +1,8 @@ package io.openems.common.jsonrpc.serialization; +/** + * Parent class for all JsonPath interfaces. + */ public interface JsonPath { } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePath.java new file mode 100644 index 00000000000..5c1509c5cc2 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePath.java @@ -0,0 +1,303 @@ +package io.openems.common.jsonrpc.serialization; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserChannelAddress; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserEnum; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserLocalDate; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserSemanticVersion; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserString; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserUuid; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserZonedDateTime; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; + +public interface JsonPrimitivePath extends JsonPath { + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath}. + * + * @param the actual type of the string value + * @param parser the parser to parse the string + * @return the current element as a {@link StringPath} + */ + public StringPath getAsStringPath(StringParser parser); + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which just + * contains its raw string as the parsed value. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathString() { + return this.getAsStringPath(new StringParserString()); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link ChannelAddress} as its parsed value. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathChannelAddress() { + return this.getAsStringPath(new StringParserChannelAddress()); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link UUID} as its parsed value. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathUuid() { + return this.getAsStringPath(new StringParserUuid()); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link SemanticVersion} as its parsed value. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathSemanticVersion() { + return this.getAsStringPath(new StringParserSemanticVersion()); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link Enum} as its parsed value. + * + * @param the type of the enum value + * @param enumClass the type class of the enum + * @return the current element as a {@link StringPath} + */ + public default > StringPath getAsStringPathEnum(Class enumClass) { + return this.getAsStringPath(new StringParserEnum<>(enumClass)); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link ZonedDateTime} as its parsed value. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathZonedDateTime(DateTimeFormatter formatter) { + return this.getAsStringPath(new StringParserZonedDateTime(formatter)); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link ZonedDateTime} as its parsed value. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathZonedDateTime() { + return this.getAsStringPath(new StringParserZonedDateTime()); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link LocalDate} as its parsed value. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathLocalDate(DateTimeFormatter formatter) { + return this.getAsStringPath(new StringParserLocalDate(formatter)); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link StringPath} which + * contains a {@link LocalDate} as its parsed value. + * + * @return the current element as a {@link StringPath} + */ + public default StringPath getAsStringPathLocalDate() { + return this.getAsStringPath(new StringParserLocalDate()); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link String}. + * + * @return the current element as a {@link String} + */ + public default String getAsString() { + return this.getAsStringPathString().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link ChannelAddress}. + * + * @return the current element as a {@link ChannelAddress} + */ + public default ChannelAddress getAsChannelAddress() { + return this.getAsStringPathChannelAddress().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link UUID}. + * + * @return the current element as a {@link UUID} + */ + public default UUID getAsUuid() { + return this.getAsStringPathUuid().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link SemanticVersion}. + * + * @return the current element as a {@link SemanticVersion} + */ + public default SemanticVersion getAsSemanticVersion() { + return this.getAsStringPathSemanticVersion().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link Enum}. + * + * @param the type of the enum value + * @param enumClass the type class of the enum + * @return the current element as a {@link Enum} + */ + public default > T getAsEnum(Class enumClass) { + return this.getAsStringPathEnum(enumClass).get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link ZonedDateTime}. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link ZonedDateTime} + */ + public default ZonedDateTime getAsZonedDateTime(DateTimeFormatter formatter) { + return this.getAsStringPathZonedDateTime(formatter).get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link ZonedDateTime}. + * + * @return the current element as a {@link ZonedDateTime} + */ + public default ZonedDateTime getAsZonedDateTime() { + return this.getAsStringPathZonedDateTime().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link LocalDate}. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link LocalDate} + */ + public default LocalDate getAsLocalDate(DateTimeFormatter formatter) { + return this.getAsStringPathLocalDate(formatter).get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link LocalDate}. + * + * @return the current element as a {@link LocalDate} + */ + public default LocalDate getAsLocalDate() { + return this.getAsStringPathLocalDate().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link NumberPath}. + * + * @return the current element as a {@link NumberPath} + */ + public NumberPath getAsNumberPath(); + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link Number}. + * + * @return the current element as a {@link Number} + */ + public default Number getAsNumber() { + return this.getAsNumberPath().get(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a double. + * + * @return the current element as a double + */ + public default double getAsDouble() { + return this.getAsNumberPath().getAsDouble(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a float. + * + * @return the current element as a float + */ + public default float getAsFloat() { + return this.getAsNumberPath().getAsFloat(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a long. + * + * @return the current element as a long + */ + public default long getAsLong() { + return this.getAsNumberPath().getAsLong(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a integer. + * + * @return the current element as a integer + */ + public default int getAsInt() { + return this.getAsNumberPath().getAsInt(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a short. + * + * @return the current element as a short + */ + public default short getAsShort() { + return this.getAsNumberPath().getAsShort(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a byte. + * + * @return the current element as a byte + */ + public default byte getAsByte() { + return this.getAsNumberPath().getAsByte(); + } + + /** + * Gets the current {@link JsonPrimitivePath} as a {@link BooleanPath}. + * + * @return the current element as a {@link BooleanPath} + */ + public BooleanPath getAsBooleanPath(); + + /** + * Gets the current {@link JsonPrimitivePath} as a primitive {@link Boolean}. + * + * @return the current element as a primitive {@link Boolean} + */ + public default boolean getAsBoolean() { + return this.getAsBooleanPath().get(); + } + + /** + * Gets the current {@link JsonPrimitive} element. + * + * @return the raw element + */ + public JsonPrimitive get(); + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathActual.java new file mode 100644 index 00000000000..d2abd18ba4b --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathActual.java @@ -0,0 +1,105 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.Objects; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.exceptions.OpenemsRuntimeException; + +public final class JsonPrimitivePathActual { + + public static class JsonPrimitivePathActualNonNull implements JsonPrimitivePath { + + private final JsonPrimitive element; + + public JsonPrimitivePathActualNonNull(JsonPrimitive element) { + super(); + this.element = Objects.requireNonNull(element); + } + + @Override + public StringPath getAsStringPath(StringParser parser) { + return new StringPathActual.StringPathActualNonNull<>(parseToStrictString(this.element), parser::parse); + } + + @Override + public NumberPath getAsNumberPath() { + return new NumberPathActual.NumberPathActualNonNull(parseToStrictNumber(this.element)); + } + + @Override + public BooleanPath getAsBooleanPath() { + return new BooleanPathActual.BooleanPathActualNonNull(parseToStrictBoolean(this.element)); + } + + @Override + public JsonPrimitive get() { + return this.element; + } + + } + + public static class JsonPrimitivePathActualNullable implements JsonPrimitivePathNullable { + + private final JsonPrimitive element; + + public JsonPrimitivePathActualNullable(JsonPrimitive element) { + super(); + this.element = element; + } + + @Override + public BooleanPathNullable getAsBooleanPathNullable() { + return new BooleanPathActual.BooleanPathActualNullable( + this.element == null ? null : parseToStrictBoolean(this.element)); + } + + @Override + public NumberPathNullable getAsNumberPathNullable() { + return new NumberPathActual.NumberPathActualNullable( + this.element == null ? null : parseToStrictNumber(this.element)); + } + + @Override + public StringPathNullable getAsStringPathNullable(StringParser parser) { + return new StringPathActual.StringPathActualNullable<>( + this.element == null ? null : parseToStrictString(this.element), parser::parse); + } + + @Override + public boolean isPresent() { + return this.element != null; + } + + @Override + public JsonPrimitive getOrNull() { + return this.element; + } + + } + + private JsonPrimitivePathActual() { + } + + private static String parseToStrictString(JsonPrimitive element) { + if (!element.isString()) { + throw new OpenemsRuntimeException("Unable to parse \"" + element + "\" to String"); + } + return element.getAsString(); + } + + private static boolean parseToStrictBoolean(JsonPrimitive element) { + if (!element.isBoolean()) { + throw new OpenemsRuntimeException("Unable to parse \"" + element + "\" to boolean"); + } + return element.getAsBoolean(); + } + + private static Number parseToStrictNumber(JsonPrimitive element) { + if (!element.isNumber()) { + throw new OpenemsRuntimeException("Unable to parse \"" + element + "\" to Number"); + } + return element.getAsNumber(); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathDummy.java new file mode 100644 index 00000000000..bd780d9bfa3 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathDummy.java @@ -0,0 +1,114 @@ +package io.openems.common.jsonrpc.serialization; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.utils.JsonUtils; + +public final class JsonPrimitivePathDummy { + + /** + * Builds a simple path for primitives. + * + * @param type the type of the path + * @param optional true if the path is optional else false + * @return the created path + */ + public static JsonElement buildPath(// + final String type, // + final boolean optional // + ) { + return JsonUtils.buildJsonObject() // + .addProperty("type", type) // + .addProperty("optional", optional) // + .build(); + } + + public static final class JsonPrimitivePathDummyNonNull implements JsonPrimitivePath, JsonPathDummy { + + private JsonPathDummy actualType; + + @Override + public StringPath getAsStringPath(StringParser parser) { + final var example = parser.getExample(); + return this.withPath(new StringPathDummy.StringPathDummyNonNull<>(example.raw(), example.value())); + } + + @Override + public NumberPath getAsNumberPath() { + return this.withPath(new NumberPathDummy.NumberPathDummyNonNull()); + } + + @Override + public BooleanPath getAsBooleanPath() { + return this.withPath(new BooleanPathDummy.BooleanPathDummyNonNull()); + } + + @Override + public JsonPrimitive get() { + return new JsonPrimitive(0); + } + + @Override + public JsonElement buildPath() { + if (this.actualType != null) { + return this.actualType.buildPath(); + } + return JsonPrimitivePathDummy.buildPath("primitive", false); + } + + private T withPath(T type) { + this.actualType = type; + return type; + } + + } + + public static final class JsonPrimitivePathDummyNullable implements JsonPrimitivePathNullable, JsonPathDummy { + + private JsonPathDummy actualType; + + @Override + public BooleanPathNullable getAsBooleanPathNullable() { + return this.withPath(new BooleanPathDummy.BooleanPathDummyNullable()); + } + + @Override + public NumberPathNullable getAsNumberPathNullable() { + return this.withPath(new NumberPathDummy.NumberPathDummyNullable()); + } + + @Override + public StringPathNullable getAsStringPathNullable(StringParser parser) { + return this.withPath(new StringPathDummy.StringPathDummyNullable<>()); + } + + @Override + public boolean isPresent() { + return false; + } + + @Override + public JsonPrimitive getOrNull() { + return null; + } + + @Override + public JsonElement buildPath() { + if (this.actualType != null) { + return this.actualType.buildPath(); + } + return JsonPrimitivePathDummy.buildPath("primitive", true); + } + + private T withPath(T type) { + this.actualType = type; + return type; + } + + } + + private JsonPrimitivePathDummy() { + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathNullable.java new file mode 100644 index 00000000000..489b9eefa7f --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonPrimitivePathNullable.java @@ -0,0 +1,534 @@ +package io.openems.common.jsonrpc.serialization; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Optional; +import java.util.UUID; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserChannelAddress; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserEnum; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserLocalDate; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserSemanticVersion; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserString; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserUuid; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserZonedDateTime; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; + +public interface JsonPrimitivePathNullable extends JsonPath { + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable}. + * + * @param the actual type of the string value + * @param parser the parser to parse the string + * @return the current element as a {@link StringPathNullable} + */ + public StringPathNullable getAsStringPathNullable(StringParser parser); + + /** + * Gets the current {@link JsonPrimitivePathNullable} parsed if present else + * null. + * + * @param the type of the result object + * @param parser the parser to use to parse the string value + * @return the parsed object or null if not present + */ + public default T getAsStringParsedOrNull(StringParser parser) { + return this.getAsStringPathNullable(parser).getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional}. + * + * @param the type of the result object + * @param parser the parser to use to parse the string value + * @return a {@link Optional} of the parsed string or empty if not present + */ + public default Optional getAsOptionalStringParsed(StringParser parser) { + return this.getAsStringPathNullable(parser).getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which just contains its raw string as the parsed + * value. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableString() { + return this.getAsStringPathNullable(new StringParserString()); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link ChannelAddress} as its + * parsed value. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableChannelAddress() { + return this.getAsStringPathNullable(new StringParserChannelAddress()); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link UUID} as its parsed value. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableUuid() { + return this.getAsStringPathNullable(new StringParserUuid()); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link SemanticVersion} as its + * parsed value. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableSemanticVersion() { + return this.getAsStringPathNullable(new StringParserSemanticVersion()); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link Enum} as its parsed value. + * + * @param the type of the enum value + * @param enumClass the type class of the enum + * @return the current element as a {@link StringPathNullable} + */ + public default > StringPathNullable getAsStringPathNullableEnum(Class enumClass) { + return this.getAsStringPathNullable(new StringParserEnum<>(enumClass)); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link ZonedDateTime} as its + * parsed value. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableZonedDateTime(DateTimeFormatter formatter) { + return this.getAsStringPathNullable(new StringParserZonedDateTime(formatter)); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link ZonedDateTime} as its + * parsed value. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableZonedDateTime() { + return this.getAsStringPathNullable(new StringParserZonedDateTime()); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link LocalDate} as its parsed + * value. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableLocalDate(DateTimeFormatter formatter) { + return this.getAsStringPathNullable(new StringParserLocalDate(formatter)); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link StringPathNullable} which contains a {@link LocalDate} as its parsed + * value. + * + * @return the current element as a {@link StringPathNullable} + */ + public default StringPathNullable getAsStringPathNullableLocalDate() { + return this.getAsStringPathNullable(new StringParserLocalDate()); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link String} or + * null. + * + * @return the current element as a {@link String} or null if not existing + */ + public default String getAsStringOrNull() { + return this.getAsStringPathNullableString().getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link String}. + * + * @return the current element as a {@link Optional} of {@link String} + */ + public default Optional getAsOptionalString() { + return this.getAsStringPathNullableString().getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link ChannelAddress} or null. + * + * @return the current element as a {@link ChannelAddress} or null if not + * existing + */ + public default ChannelAddress getAsChannelAddressOrNull() { + return this.getAsStringPathNullableChannelAddress().getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link ChannelAddress}. + * + * @return the current element as a {@link Optional} of {@link ChannelAddress} + */ + public default Optional getAsOptionalChannelAddress() { + return this.getAsStringPathNullableChannelAddress().getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link UUID} or + * null. + * + * @return the current element as a {@link UUID} or null if not existing + */ + public default UUID getAsUuidOrNull() { + return this.getAsStringPathNullableUuid().getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link UUID}. + * + * @return the current element as a {@link Optional} of {@link UUID} + */ + public default Optional getAsOptionalUuid() { + return this.getAsStringPathNullableUuid().getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link SemanticVersion} or null. + * + * @return the current element as a {@link SemanticVersion} or null if not + * existing + */ + public default SemanticVersion getAsSemanticVersionOrNull() { + return this.getAsStringPathNullableSemanticVersion().getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link SemanticVersion}. + * + * @return the current element as a {@link Optional} of {@link SemanticVersion} + */ + public default Optional getAsOptionalSemanticVersion() { + return this.getAsStringPathNullableSemanticVersion().getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Enum} or + * null. + * + * @param the type of the enum value + * @param enumClass the type class of the enum + * @return the current element as a {@link Enum} or null if not existing + */ + public default > T getAsEnumOrNull(Class enumClass) { + return this.getAsStringPathNullableEnum(enumClass).getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link Enum}. + * + * @param the type of the enum value + * @param enumClass the type class of the enum + * @return the current element as a {@link Optional} of {@link Enum} + */ + public default > Optional getAsOptionalEnum(Class enumClass) { + return this.getAsStringPathNullableEnum(enumClass).getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link ZonedDateTime} + * or null. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link Enum} or null if not existing + */ + public default ZonedDateTime getAsZonedDateTimeOrNull(DateTimeFormatter formatter) { + return this.getAsStringPathNullableZonedDateTime(formatter).getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link ZonedDateTime} + * or null. + * + * @return the current element as a {@link ZonedDateTime} or null if not + * existing + */ + public default ZonedDateTime getAsZonedDateTimeOrNull() { + return this.getAsStringPathNullableZonedDateTime().getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link ZonedDateTime}. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link Optional} of {@link ZonedDateTime} + */ + public default Optional getAsOptionalZonedDateTime(DateTimeFormatter formatter) { + return this.getAsStringPathNullableZonedDateTime(formatter).getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link ZonedDateTime}. + * + * @return the current element as a {@link Optional} of {@link ZonedDateTime} + */ + public default Optional getAsOptionalZonedDateTime() { + return this.getAsStringPathNullableZonedDateTime().getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link LocalDate} or + * null. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link LocalDate} or null if not existing + */ + public default LocalDate getAsLocalDateOrNull(DateTimeFormatter formatter) { + return this.getAsStringPathNullableLocalDate(formatter).getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link LocalDate} or + * null. + * + * @return the current element as a {@link LocalDate} or null if not existing + */ + public default LocalDate getAsLocalDateOrNull() { + return this.getAsStringPathNullableLocalDate().getOrNull(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link LocalDate}. + * + * @param formatter the {@link DateTimeFormatter} used to parse the string + * @return the current element as a {@link Optional} of {@link LocalDate} + */ + public default Optional getAsOptionalLocalDate(DateTimeFormatter formatter) { + return this.getAsStringPathNullableLocalDate(formatter).getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a {@link Optional} of + * {@link LocalDate}. + * + * @return the current element as a {@link Optional} of {@link LocalDate} + */ + public default Optional getAsOptionalLocalDate() { + return this.getAsStringPathNullableLocalDate().getOptional(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link NumberPathNullable}. + * + * @return the current element as a {@link NumberPathNullable} + */ + public NumberPathNullable getAsNumberPathNullable(); + + /** + * Gets the current value as a double or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a double or the default value if the current + * value is not present + * @see NumberPathNullable#getAsDoubleOrDefault(double) + */ + public default double getAsDoubleOrDefault(double defaultValue) { + return this.getAsNumberPathNullable().getAsDoubleOrDefault(defaultValue); + } + + /** + * Gets the current value as a float or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a float or the default value if the current + * value is not present + * @see NumberPathNullable#getAsFloatOrDefault(double) + */ + public default float getAsFloatOrDefault(float defaultValue) { + return this.getAsNumberPathNullable().getAsFloatOrDefault(defaultValue); + } + + /** + * Gets the current value as a long or returns the provided default value if the + * current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a long or the default value if the current value + * is not present + * @see NumberPathNullable#getAsLongOrDefault(double) + */ + public default long getAsLongOrDefault(long defaultValue) { + return this.getAsNumberPathNullable().getAsLongOrDefault(defaultValue); + } + + /** + * Gets the current value as a integer or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a integer or the default value if the current + * value is not present + * @see NumberPathNullable#getAsIntOrDefault(double) + */ + public default int getAsIntOrDefault(int defaultValue) { + return this.getAsNumberPathNullable().getAsIntOrDefault(defaultValue); + } + + /** + * Gets the current value as a short or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a short or the default value if the current + * value is not present + * @see NumberPathNullable#getAsShortOrDefault(double) + */ + public default short getAsShortOrDefault(short defaultValue) { + return this.getAsNumberPathNullable().getAsShortOrDefault(defaultValue); + } + + /** + * Gets the current value as a byte or returns the provided default value if the + * current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a byte or the default value if the current value + * is not present + * @see NumberPathNullable#getAsByteOrDefault(double) + */ + public default byte getAsByteOrDefault(byte defaultValue) { + return this.getAsNumberPathNullable().getAsByteOrDefault(defaultValue); + } + + /** + * Gets the current value as a {@link Optional} of double. + * + * @return the current value as a {@link Optional} of double + * @see NumberPathNullable#getAsOptionalDouble() + */ + public default Optional getAsOptionalDouble() { + return this.getAsNumberPathNullable().getAsOptionalDouble(); + } + + /** + * Gets the current value as a {@link Optional} of float. + * + * @return the current value as a {@link Optional} of float + * @see NumberPathNullable#getAsOptionalFloat() + */ + public default Optional getAsOptionalFloat() { + return this.getAsNumberPathNullable().getAsOptionalFloat(); + } + + /** + * Gets the current value as a {@link Optional} of long. + * + * @return the current value as a {@link Optional} of long + * @see NumberPathNullable#getAsOptionalLong() + */ + public default Optional getAsOptionalLong() { + return this.getAsNumberPathNullable().getAsOptionalLong(); + } + + /** + * Gets the current value as a {@link Optional} of integer. + * + * @return the current value as a {@link Optional} of integer + * @see NumberPathNullable#getAsOptionalInt() + */ + public default Optional getAsOptionalInt() { + return this.getAsNumberPathNullable().getAsOptionalInt(); + } + + /** + * Gets the current value as a {@link Optional} of short. + * + * @return the current value as a {@link Optional} of short + * @see NumberPathNullable#getAsOptionalShort() + */ + public default Optional getAsOptionalShort() { + return this.getAsNumberPathNullable().getAsOptionalShort(); + } + + /** + * Gets the current value as a {@link Optional} of byte. + * + * @return the current value as a {@link Optional} of byte + * @see NumberPathNullable#getAsOptionalByte() + */ + public default Optional getAsOptionalByte() { + return this.getAsNumberPathNullable().getAsOptionalByte(); + } + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a + * {@link BooleanPathNullable}. + * + * @return the current element as a {@link BooleanPathNullable} + */ + public BooleanPathNullable getAsBooleanPathNullable(); + + /** + * Gets the current {@link JsonPrimitivePathNullable} as a primitive + * {@link Boolean} if present otherwise returns the provided default value. + * + * @param defaultValue the default value to provide if the current values is not + * present + * @return the current value as a boolean if present; else the default value + */ + public default boolean getAsBooleanOrDefault(boolean defaultValue) { + return this.getAsBooleanPathNullable().getOrDefault(defaultValue); + } + + /** + * Checks if the current value is present. + * + * @return true if the current value is present; else false + */ + public boolean isPresent(); + + /** + * Gets the JsonPrimitive value of the current path. + * + * @return the value; or null if not present + */ + public JsonPrimitive getOrNull(); + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java index e1606567e2e..8fda33ecf62 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializer.java @@ -3,7 +3,11 @@ import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; import static io.openems.common.utils.JsonUtils.toJsonArray; +import java.lang.reflect.Array; +import java.util.Arrays; import java.util.List; +import java.util.Set; +import java.util.function.IntFunction; import com.google.gson.JsonElement; @@ -40,7 +44,7 @@ public interface JsonSerializer { * @return the deserialized object from the {@link JsonElement} */ public default T deserialize(JsonElement json) { - return this.deserializePath(new JsonElementPathActual(json)); + return this.deserializePath(new JsonElementPathActual.JsonElementPathActualNonNull(json)); } /** @@ -57,4 +61,34 @@ public default JsonSerializer> toListSerializer() { .collect(toJsonArray())); } + /** + * Creates a new {@link JsonSerializer} which is able to serialize {@link Set + * Sets} with their generic type of the current {@link JsonSerializer}. + * + * @return the new {@link JsonSerializer} of a {@link Set} + */ + public default JsonSerializer> toSetSerializer() { + return jsonSerializer(// + json -> json.getAsJsonArrayPath().getAsSet(this), // + obj -> obj.stream() // + .map(this::serialize) // + .collect(toJsonArray())); + } + + /** + * Creates a new {@link JsonSerializer} which is able to serialize {@link Array + * Arrays} with their type of the current {@link JsonSerializer}. + * + * @param generator a function which produces a new array of the desired type + * and the provided length + * @return the new {@link JsonSerializer} of a {@link Array} + */ + public default JsonSerializer toArraySerializer(IntFunction generator) { + return jsonSerializer(// + json -> json.getAsJsonArrayPath().getAsList(this).toArray(generator), // + obj -> Arrays.stream(obj) // + .map(this::serialize) // + .collect(toJsonArray())); + } + } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java index 2052d35d0b8..995a3460521 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/JsonSerializerUtil.java @@ -1,14 +1,22 @@ package io.openems.common.jsonrpc.serialization; +import static io.openems.common.utils.FunctionUtils.lazySingleton; + import java.util.function.Function; +import java.util.function.Supplier; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; -import com.google.common.base.Supplier; import com.google.gson.JsonArray; import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; public final class JsonSerializerUtil { + private static final Logger LOG = LoggerFactory.getLogger(JsonSerializerUtil.class); + /** * Creates a {@link JsonSerializer} for a empty {@link JsonObject}. * @@ -22,6 +30,15 @@ public static JsonSerializer emptyObjectSerializer(// return jsonObjectSerializer(json -> object.get(), json -> new JsonObject()); } + /** + * Returns a {@link JsonSerializer} for a {@link String}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer stringSerializer() { + return jsonSerializer(String.class, JsonElementPath::getAsString, JsonPrimitive::new); + } + /** * Creates a {@link JsonSerializer} for the provided type. * @@ -115,9 +132,7 @@ public static JsonSerializer jsonSerializer(// Function toObjMapper, // Function toJsonMapper // ) { - final var path = new JsonElementPathDummy(); - toObjMapper.apply(path); - return new SimpleJsonSerializer(new SerializerDescriptor(path), toJsonMapper, toObjMapper); + return new SimpleJsonSerializer<>(toJsonMapper, toObjMapper); } private JsonSerializerUtil() { @@ -125,21 +140,20 @@ private JsonSerializerUtil() { private static final class SimpleJsonSerializer implements JsonSerializer { - private final SerializerDescriptor descriptor; + private final Supplier descriptor; private final Function serialize; private final Function deserialize; - public SimpleJsonSerializer(SerializerDescriptor descriptor, Function serialize, - Function deserialize) { + public SimpleJsonSerializer(Function serialize, Function deserialize) { super(); - this.descriptor = descriptor; + this.descriptor = lazySingleton(this::createSerializerDescriptor); this.serialize = serialize; this.deserialize = deserialize; } @Override public SerializerDescriptor descriptor() { - return this.descriptor; + return this.descriptor.get(); } @Override @@ -152,6 +166,16 @@ public T deserializePath(JsonElementPath a) { return this.deserialize.apply(a); } + private SerializerDescriptor createSerializerDescriptor() { + final var path = new JsonElementPathDummy.JsonElementPathDummyNonNull(); + try { + this.deserialize.apply(path); + } catch (RuntimeException e) { + LOG.error("Unexpected error while trying to create SerializerDescriptor", e); + } + return new SerializerDescriptor(path); + } + } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPath.java new file mode 100644 index 00000000000..22a64c000f7 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPath.java @@ -0,0 +1,66 @@ +package io.openems.common.jsonrpc.serialization; + +public interface NumberPath extends JsonPath { + + /** + * Gets the string value of the current path. + * + * @return the value + */ + public Number get(); + + /** + * Gets the current {@link Number} as a primitive {@link Double}. + * + * @return the double value + */ + public default double getAsDouble() { + return this.get().doubleValue(); + } + + /** + * Gets the current {@link Number} as a primitive {@link Float}. + * + * @return the float value + */ + public default float getAsFloat() { + return this.get().floatValue(); + } + + /** + * Gets the current {@link Number} as a primitive {@link Long}. + * + * @return the long value + */ + public default long getAsLong() { + return this.get().longValue(); + } + + /** + * Gets the current {@link Number} as a primitive {@link Integer}. + * + * @return the integer value + */ + public default int getAsInt() { + return this.get().intValue(); + } + + /** + * Gets the current {@link Number} as a primitive {@link Short}. + * + * @return the short value + */ + public default short getAsShort() { + return this.get().shortValue(); + } + + /** + * Gets the current {@link Number} as a primitive {@link Byte}. + * + * @return the byte value + */ + public default byte getAsByte() { + return this.get().byteValue(); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathActual.java new file mode 100644 index 00000000000..df1883a57a2 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathActual.java @@ -0,0 +1,47 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.Objects; + +public final class NumberPathActual { + + public static final class NumberPathActualNonNull implements NumberPath { + + private final Number element; + + public NumberPathActualNonNull(Number element) { + super(); + this.element = Objects.requireNonNull(element); + } + + @Override + public Number get() { + return this.element; + } + + } + + public static final class NumberPathActualNullable implements NumberPathNullable { + + private final Number element; + + public NumberPathActualNullable(Number element) { + super(); + this.element = element; + } + + @Override + public boolean isPresent() { + return this.element != null; + } + + @Override + public Number getOrNull() { + return this.element; + } + + } + + private NumberPathActual() { + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathDummy.java new file mode 100644 index 00000000000..5511eeb0c8b --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathDummy.java @@ -0,0 +1,43 @@ +package io.openems.common.jsonrpc.serialization; + +import com.google.gson.JsonElement; + +public final class NumberPathDummy { + + public static final class NumberPathDummyNonNull implements NumberPath, JsonPathDummy { + + @Override + public Number get() { + return 0; + } + + @Override + public JsonElement buildPath() { + return JsonPrimitivePathDummy.buildPath("number", false); + } + + } + + public static final class NumberPathDummyNullable implements NumberPathNullable, JsonPathDummy { + + @Override + public boolean isPresent() { + return false; + } + + @Override + public Number getOrNull() { + return null; + } + + @Override + public JsonElement buildPath() { + return JsonPrimitivePathDummy.buildPath("number", true); + } + + } + + private NumberPathDummy() { + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathNullable.java new file mode 100644 index 00000000000..416b2a8beef --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/NumberPathNullable.java @@ -0,0 +1,159 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.Optional; + +public interface NumberPathNullable extends JsonPath { + + /** + * Checks if the current value is present. + * + * @return true if the current value is present; else false + */ + public boolean isPresent(); + + /** + * Gets the string value of the current path. + * + * @return the value + */ + public Number getOrNull(); + + /** + * Gets the current value as a double or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a double or the default value if the current + * value is not present + */ + public default double getAsDoubleOrDefault(double defaultValue) { + return this.isPresent() ? this.getOrNull().doubleValue() : defaultValue; + } + + /** + * Gets the current value as a float or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a float or the default value if the current + * value is not present + */ + public default float getAsFloatOrDefault(float defaultValue) { + return this.isPresent() ? this.getOrNull().floatValue() : defaultValue; + } + + /** + * Gets the current value as a long or returns the provided default value if the + * current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a long or the default value if the current value + * is not present + */ + public default long getAsLongOrDefault(long defaultValue) { + return this.isPresent() ? this.getOrNull().longValue() : defaultValue; + } + + /** + * Gets the current value as a integer or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a integer or the default value if the current + * value is not present + */ + public default int getAsIntOrDefault(int defaultValue) { + return this.isPresent() ? this.getOrNull().intValue() : defaultValue; + } + + /** + * Gets the current value as a short or returns the provided default value if + * the current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a short or the default value if the current + * value is not present + */ + public default short getAsShortOrDefault(short defaultValue) { + return this.isPresent() ? this.getOrNull().shortValue() : defaultValue; + } + + /** + * Gets the current value as a byte or returns the provided default value if the + * current element is not present. + * + * @param defaultValue the default value to provide in case the current value is + * not present + * @return the current value as a byte or the default value if the current value + * is not present + */ + public default byte getAsByteOrDefault(byte defaultValue) { + return this.isPresent() ? this.getOrNull().byteValue() : defaultValue; + } + + /** + * Gets the current value as a {@link Optional} of double. + * + * @return the current value as a {@link Optional} of double + */ + public default Optional getAsOptionalDouble() { + return Optional.ofNullable(this.getOrNull()) // + .map(Number::doubleValue); + } + + /** + * Gets the current value as a {@link Optional} of float. + * + * @return the current value as a {@link Optional} of float + */ + public default Optional getAsOptionalFloat() { + return Optional.ofNullable(this.getOrNull()) // + .map(Number::floatValue); + } + + /** + * Gets the current value as a {@link Optional} of long. + * + * @return the current value as a {@link Optional} of long + */ + public default Optional getAsOptionalLong() { + return Optional.ofNullable(this.getOrNull()) // + .map(Number::longValue); + } + + /** + * Gets the current value as a {@link Optional} of integer. + * + * @return the current value as a {@link Optional} of integer + */ + public default Optional getAsOptionalInt() { + return Optional.ofNullable(this.getOrNull()) // + .map(Number::intValue); + } + + /** + * Gets the current value as a {@link Optional} of short. + * + * @return the current value as a {@link Optional} of short + */ + public default Optional getAsOptionalShort() { + return Optional.ofNullable(this.getOrNull()) // + .map(Number::shortValue); + } + + /** + * Gets the current value as a {@link Optional} of byte. + * + * @return the current value as a {@link Optional} of byte + */ + public default Optional getAsOptionalByte() { + return Optional.ofNullable(this.getOrNull()) // + .map(Number::byteValue); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/PolymorphicSerializer.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/PolymorphicSerializer.java new file mode 100644 index 00000000000..119e7c26d3b --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/PolymorphicSerializer.java @@ -0,0 +1,99 @@ +package io.openems.common.jsonrpc.serialization; + +import static java.util.stream.Collectors.toMap; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; + +import com.google.gson.JsonElement; + +import io.openems.common.exceptions.OpenemsRuntimeException; + +public final class PolymorphicSerializer { + + private record PolymorphicSerializerEntry(Class clazz, JsonSerializer serializer, String identifier) { + + } + + public static final class PolymorphicSerializerBuilder { + + private final List> entries = new ArrayList<>(); + + private PolymorphicSerializerBuilder() { + } + + /** + * Adds a {@link JsonSerializer} to use for a subtype. + * + * @param the subtype + * @param clazz the {@link Class} of the subtype + * @param serializer the {@link JsonSerializer} + * @param identifier the {@link String} identifier in the json + * @return this + */ + public PolymorphicSerializerBuilder add(// + Class clazz, // + JsonSerializer serializer, // + String identifier // + ) { + this.entries.add(new PolymorphicSerializerEntry(clazz, serializer, identifier)); + return this; + } + + public PolymorphicSerializer build() { + return new PolymorphicSerializer<>(this.entries); + } + + } + + /** + * Creates a builder for a {@link PolymorphicSerializer}. + * + * @param the type of the {@link PolymorphicSerializer} + * @return the builder + */ + public static PolymorphicSerializerBuilder create() { + return new PolymorphicSerializerBuilder(); + } + + private final Map> serializerByIdentifier; + private final Map, JsonSerializer> serializerByClass; + + private PolymorphicSerializer(List> entries) { + this.serializerByIdentifier = entries.stream() + .collect(toMap(PolymorphicSerializerEntry::identifier, PolymorphicSerializerEntry::serializer)); + this.serializerByClass = entries.stream() + .collect(toMap(PolymorphicSerializerEntry::clazz, PolymorphicSerializerEntry::serializer)); + } + + /** + * Gets a map with identifier to {@link JsonSerializer} values. + * + * @return the map + */ + public Map> serializerByIdentifier() { + return this.serializerByIdentifier; + } + + /** + * Serializes the object to a json with the {@link JsonSerializer + * JsonSerializers} set in this instance. + * + * @param the type of the object to serialize + * @param object the object to serialize + * @return a {@link JsonElement} representing the object + */ + @SuppressWarnings("unchecked") + public JsonElement serialize(D object) { + @SuppressWarnings("rawtypes") + final var serializer = (JsonSerializer) this.serializerByClass.get(object.getClass()); + + if (serializer == null) { + throw new OpenemsRuntimeException("Missing serializer for class " + object.getClass().getCanonicalName()); + } + + return serializer.serialize(object); + } + +} \ No newline at end of file diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java index 568cf1a2c09..664566b07f0 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/SerializerDescriptor.java @@ -19,8 +19,4 @@ public JsonElement toJson() { return this.obj.buildPath(); } - public JsonElementPathDummy getObj() { - return this.obj; - } - } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringParser.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringParser.java new file mode 100644 index 00000000000..8b1caceb959 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringParser.java @@ -0,0 +1,31 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.Objects; + +public interface StringParser { + + /** + * Parses the provided string to the type of T. + * + * @param value the value to parse + * @return the parsed result object + */ + public T parse(String value); + + /** + * Provides example values of the possible outcome of this parser. + * + * @return the {@link ExampleValues} of this parser + */ + public ExampleValues getExample(); + + public record ExampleValues(String raw, T value) { + + public ExampleValues { + Objects.requireNonNull(raw); + Objects.requireNonNull(value); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java index d32c4056eb0..c20648c9b65 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPath.java @@ -1,21 +1,19 @@ package io.openems.common.jsonrpc.serialization; -import java.util.UUID; - -public interface StringPath extends JsonPath { +public interface StringPath extends JsonPath { /** * Gets the string value of the current path. * * @return the value */ - public String get(); + public String getRaw(); /** - * Gets the value as a {@link UUID}. + * Gets the parsed value of this string path. * - * @return the {@link UUID} + * @return the parsed value of this string */ - public UUID getAsUuid(); + public T get(); } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java index d0f2e2799b1..7608ed0e271 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathActual.java @@ -1,32 +1,67 @@ package io.openems.common.jsonrpc.serialization; -import java.util.UUID; +import static io.openems.common.utils.FunctionUtils.lazySingleton; -import com.google.gson.JsonElement; +import java.util.Objects; +import java.util.function.Function; +import java.util.function.Supplier; -import io.openems.common.exceptions.OpenemsRuntimeException; +public final class StringPathActual { -public class StringPathActual implements StringPath { + public static final class StringPathActualNonNull implements StringPath { - private final String element; + private final Function parser; + private final String element; + private final Supplier parsedValue; - public StringPathActual(JsonElement element) throws OpenemsRuntimeException { - super(); - if (!element.isJsonPrimitive() // - || !element.getAsJsonPrimitive().isString()) { - throw new OpenemsRuntimeException(element + " is not a String!"); + public StringPathActualNonNull(String element, Function parser) { + super(); + this.parser = Objects.requireNonNull(parser); + this.element = Objects.requireNonNull(element); + + this.parsedValue = lazySingleton(() -> this.parser.apply(this.element)); + } + + @Override + public String getRaw() { + return this.element; + } + + @Override + public T get() { + return this.parsedValue.get(); } - this.element = element.getAsString(); + } - @Override - public String get() { - return this.element; + public static final class StringPathActualNullable implements StringPathNullable { + + private final Function parser; + private final String element; + private final Supplier parsedValue; + + public StringPathActualNullable(String element, Function parser) { + super(); + this.parser = Objects.requireNonNull(parser); + this.element = element; + + this.parsedValue = this.element == null ? () -> null + : lazySingleton(() -> this.parser.apply(this.element)); + } + + @Override + public String getRawOrNull() { + return this.element; + } + + @Override + public T getOrNull() { + return this.parsedValue.get(); + } + } - @Override - public UUID getAsUuid() { - return UUID.fromString(this.element); + private StringPathActual() { } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java index 5cd61d65a25..15b063d8a61 100644 --- a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathDummy.java @@ -1,28 +1,60 @@ package io.openems.common.jsonrpc.serialization; -import java.util.UUID; - import com.google.gson.JsonElement; -import io.openems.common.utils.JsonUtils; +public abstract class StringPathDummy implements JsonPathDummy { -public class StringPathDummy implements StringPath, JsonPathDummy { + private final boolean nullable; - @Override - public String get() { - return ""; + private StringPathDummy(boolean nullable) { + super(); + this.nullable = nullable; } - @Override - public UUID getAsUuid() { - return UUID.randomUUID(); + public static final class StringPathDummyNonNull extends StringPathDummy implements StringPath { + + private final String rawValue; + private final T value; + + public StringPathDummyNonNull(String rawValue, T value) { + super(false); + this.rawValue = rawValue; + this.value = value; + } + + @Override + public String getRaw() { + return this.rawValue; + } + + @Override + public T get() { + return this.value; + } + + } + + public static final class StringPathDummyNullable extends StringPathDummy implements StringPathNullable { + + public StringPathDummyNullable() { + super(true); + } + + @Override + public String getRawOrNull() { + return null; + } + + @Override + public T getOrNull() { + return null; + } + } @Override public JsonElement buildPath() { - return JsonUtils.buildJsonObject() // - .addProperty("type", "string") // - .build(); + return JsonPrimitivePathDummy.buildPath("string", this.nullable); } } diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathNullable.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathNullable.java new file mode 100644 index 00000000000..35929d03d98 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathNullable.java @@ -0,0 +1,39 @@ +package io.openems.common.jsonrpc.serialization; + +import java.util.Optional; + +public interface StringPathNullable extends JsonPath { + + /** + * Gets the raw string value of the current path. + * + * @return the value + */ + public String getRawOrNull(); + + /** + * Gets the parsed string value of the current path. + * + * @return the value + */ + public T getOrNull(); + + /** + * Gets the current raw value as a {@link Optional}. + * + * @return a {@link Optional} of the current raw value + */ + public default Optional getRawOptional() { + return Optional.ofNullable(this.getRawOrNull()); + } + + /** + * Gets the current value as a {@link Optional}. + * + * @return a {@link Optional} of the current value + */ + public default Optional getOptional() { + return Optional.ofNullable(this.getOrNull()); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathParser.java b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathParser.java new file mode 100644 index 00000000000..6142c5f4a8e --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/serialization/StringPathParser.java @@ -0,0 +1,161 @@ +package io.openems.common.jsonrpc.serialization; + +import java.time.LocalDate; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Locale; +import java.util.UUID; + +import io.openems.common.exceptions.OpenemsRuntimeException; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; + +public final class StringPathParser { + + public static class StringParserString implements StringParser { + + @Override + public String parse(String value) { + return value; + } + + @Override + public ExampleValues getExample() { + return new ExampleValues<>("string", "string"); + } + + } + + public static class StringParserUuid implements StringParser { + + @Override + public UUID parse(String value) { + return UUID.fromString(value); + } + + @Override + public ExampleValues getExample() { + final var id = UUID.randomUUID(); + return new ExampleValues<>(id.toString(), id); + } + + } + + public static class StringParserEnum> implements StringParser { + + private final Class enumClass; + + public StringParserEnum(Class enumClass) { + super(); + this.enumClass = enumClass; + } + + @Override + public T parse(String value) { + // TODO always toUppercase? + return Enum.valueOf(this.enumClass, value.toUpperCase(Locale.ROOT)); + } + + @Override + public ExampleValues getExample() { + final var value = this.enumClass.getEnumConstants().length > 0// + ? this.enumClass.getEnumConstants()[0] + : null; + return new ExampleValues<>(value == null ? null : value.name(), value); + } + } + + public static class StringParserChannelAddress implements StringParser { + + @Override + public ChannelAddress parse(String value) { + final var parts = value.split("/"); + if (parts.length != 2) { + throw new OpenemsRuntimeException("Parse error"); + } + return new ChannelAddress(parts[0], parts[1]); + } + + @Override + public ExampleValues getExample() { + final var exampleAddress = new ChannelAddress("component0", "channel"); + return new ExampleValues<>(exampleAddress.toString(), exampleAddress); + } + + } + + public static class StringParserZonedDateTime implements StringParser { + + private final DateTimeFormatter formatter; + + public StringParserZonedDateTime(DateTimeFormatter formatter) { + super(); + this.formatter = formatter; + } + + public StringParserZonedDateTime() { + this(DateTimeFormatter.ISO_ZONED_DATE_TIME); + } + + @Override + public ZonedDateTime parse(String value) { + return ZonedDateTime.parse(value, this.formatter); + } + + @Override + public ExampleValues getExample() { + final var timestamp = ZonedDateTime.now(); + return new ExampleValues<>(timestamp.format(this.formatter), timestamp); + } + + } + + public static class StringParserLocalDate implements StringParser { + + private final DateTimeFormatter formatter; + + public StringParserLocalDate(DateTimeFormatter formatter) { + super(); + this.formatter = formatter; + } + + public StringParserLocalDate() { + this(DateTimeFormatter.ISO_LOCAL_DATE); + } + + @Override + public LocalDate parse(String value) { + return LocalDate.parse(value, this.formatter); + } + + @Override + public ExampleValues getExample() { + final var timestamp = LocalDate.now(); + return new ExampleValues<>(timestamp.format(this.formatter), timestamp); + } + + } + + public static class StringParserSemanticVersion implements StringParser { + + @Override + public SemanticVersion parse(String value) { + try { + return SemanticVersion.fromString(value); + } catch (Exception e) { + throw new OpenemsRuntimeException("Parse error", e); + } + } + + @Override + public ExampleValues getExample() { + final var version = SemanticVersion.ZERO; + return new ExampleValues<>(version.toString(), version); + } + + } + + private StringPathParser() { + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/AuthenticateWithPassword.java b/io.openems.common/src/io/openems/common/jsonrpc/type/AuthenticateWithPassword.java new file mode 100644 index 00000000000..bfb41167d97 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/AuthenticateWithPassword.java @@ -0,0 +1,135 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.List; +import java.util.Objects; + +import com.google.gson.JsonObject; + +import io.openems.common.jsonrpc.response.GetEdgesResponse.EdgeMetadata; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.AuthenticateWithPassword.Request; +import io.openems.common.jsonrpc.type.AuthenticateWithPassword.Response; +import io.openems.common.session.Language; +import io.openems.common.session.Role; +import io.openems.common.utils.JsonUtils; + +public class AuthenticateWithPassword implements EndpointRequestType { + + @Override + public String getMethod() { + return "authenticateWithPassword"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + String username, // null-able + String password // + ) { + + public Request { + Objects.requireNonNull(password); + } + + /** + * Returns a {@link JsonSerializer} for a {@link AuthenticateWithPassword}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, json -> { + return new Request(// + json.getStringOrNull("username"), // + json.getString("password") // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addPropertyIfNotNull("username", obj.username()) // + .addProperty("password", obj.password()) // + .build(); + }); + } + + } + + public record Response(// + String token, // + UserMetadata user, // + List edges // + ) { + + public record UserMetadata(// + String id, // + String name, // + Language language, // + boolean hasMultipleEdges, // + JsonObject settings, // + Role globalRole // + ) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link AuthenticateWithPassword.Response.UserMetadata}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(AuthenticateWithPassword.Response.UserMetadata.class, json -> { + return new AuthenticateWithPassword.Response.UserMetadata(// + json.getString("id"), // + json.getString("name"), // + json.getEnum("language", Language.class), // + json.getBoolean("hasMultipleEdges"), // + json.getJsonObject("settings"), // + json.getEnum("globalRole", Role.class) // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("id", obj.id()) // + .addProperty("name", obj.name()) // + .addProperty("language", obj.language())// + .addProperty("hasMultipleEdges", obj.hasMultipleEdges())// + .add("settings", obj.settings()) // + .addProperty("globalRole", obj.globalRole().name().toLowerCase()) // + .build(); + }); + } + + } + + /** + * Returns a {@link JsonSerializer} for a + * {@link AuthenticateWithPassword.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Response.class, json -> { + return new Response(// + json.getString("token"), // + json.getObject("user", UserMetadata.serializer()), // + json.getList("edges", EdgeMetadata.serializer()) // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("token", obj.token()) // + .add("user", UserMetadata.serializer().serialize(obj.user())) // + .add("edges", EdgeMetadata.serializer().toListSerializer().serialize(obj.edges())) // + .build(); + }); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/AuthenticateWithToken.java b/io.openems.common/src/io/openems/common/jsonrpc/type/AuthenticateWithToken.java new file mode 100644 index 00000000000..f6772cb6ec0 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/AuthenticateWithToken.java @@ -0,0 +1,56 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.Objects; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.AuthenticateWithToken.Request; +import io.openems.common.utils.JsonUtils; + +public class AuthenticateWithToken implements EndpointRequestType { + + @Override + public String getMethod() { + return "authenticateWithToken"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return AuthenticateWithPassword.Response.serializer(); + } + + public record Request(// + String token // + ) { + + public Request { + Objects.requireNonNull(token); + } + + /** + * Returns a {@link JsonSerializer} for a {@link AuthenticateWithToken}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, json -> { + return new Request(// + json.getString("token") // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("token", obj.token()) // + .build(); + }); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/Base64RequestType.java b/io.openems.common/src/io/openems/common/jsonrpc/type/Base64RequestType.java new file mode 100644 index 00000000000..3e64e2653f4 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/Base64RequestType.java @@ -0,0 +1,32 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.Base64; + +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.utils.JsonUtils; + +public record Base64RequestType(byte[] payload) { + + // TODO should not be encoded in first place only when send + public Base64RequestType(String payload) { + this(Base64.getDecoder().decode(payload)); + } + + /** + * Returns a {@link JsonSerializer} for a {@link Base64RequestType}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Base64RequestType.class, json -> { + return new Base64RequestType(Base64.getDecoder().decode(json.getString("payload"))); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("payload", Base64.getEncoder().encodeToString(obj.payload())) // + .build(); + }); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/CreateComponentConfig.java b/io.openems.common/src/io/openems/common/jsonrpc/type/CreateComponentConfig.java new file mode 100644 index 00000000000..52ebc41663c --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/CreateComponentConfig.java @@ -0,0 +1,83 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.List; + +import com.google.gson.JsonElement; + +import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.CreateComponentConfig.Request; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class CreateComponentConfig implements EndpointRequestType { + + @Override + public String getMethod() { + return "createComponentConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(// + String factoryPid, // + List properties // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link CreateComponentConfig.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(CreateComponentConfig.Request.class, // + json -> new CreateComponentConfig.Request(// + json.getString("factoryPid"), // + json.getList("properties", Property.serializer())), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("factoryPid", obj.factoryPid()) // + .add("properties", Property.serializer().toListSerializer().serialize(obj.properties())) // + .build()); + } + + public String getComponentId() { + return this.properties().stream() // + .filter(t -> t.getName().equals("id")) // + .findFirst() // + .map(Property::getValue) // + .map(JsonElement::getAsString) // + .orElse(null); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/DeleteComponentConfig.java b/io.openems.common/src/io/openems/common/jsonrpc/type/DeleteComponentConfig.java new file mode 100644 index 00000000000..37cf4733a28 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/DeleteComponentConfig.java @@ -0,0 +1,71 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.Objects; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.DeleteComponentConfig.Request; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class DeleteComponentConfig implements EndpointRequestType { + + @Override + public String getMethod() { + return "deleteComponentConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(// + String componentId // + ) { + + public Request { + Objects.requireNonNull(componentId); + } + + /** + * Returns a {@link JsonSerializer} for a {@link DeleteComponentConfig.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(DeleteComponentConfig.Request.class, // + json -> new DeleteComponentConfig.Request(json.getString("componentId")), + obj -> JsonUtils.buildJsonObject() // + .addProperty("componentId", obj.componentId()) // + .build()); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdge.java b/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdge.java new file mode 100644 index 00000000000..dc8cfb2ce61 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdge.java @@ -0,0 +1,87 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import io.openems.common.jsonrpc.response.GetEdgesResponse.EdgeMetadata; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.GetEdge.Request; +import io.openems.common.jsonrpc.type.GetEdge.Response; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class GetEdge implements EndpointRequestType { + + @Override + public String getMethod() { + return "getEdge"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + String edgeId // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link GetEdge.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, // + json -> new Request(// + json.getString("edgeId")), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("edgeId", obj.edgeId()) // + .build()); + } + + } + + public record Response(// + EdgeMetadata edge // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link GetEdge.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Response.class, // + json -> new Response(// + json.getObject("edge", EdgeMetadata.serializer())), // + obj -> JsonUtils.buildJsonObject() // + .add("edge", EdgeMetadata.serializer().serialize(obj.edge())) // + .build()); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdgeConfig.java b/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdgeConfig.java new file mode 100644 index 00000000000..da6eefb0938 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdgeConfig.java @@ -0,0 +1,42 @@ +package io.openems.common.jsonrpc.type; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class GetEdgeConfig implements EndpointRequestType { + + @Override + public String getMethod() { + return "getEdgeConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdges.java b/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdges.java new file mode 100644 index 00000000000..f03a420999e --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/GetEdges.java @@ -0,0 +1,90 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.List; + +import io.openems.common.jsonrpc.request.GetEdgesRequest.PaginationOptions; +import io.openems.common.jsonrpc.response.GetEdgesResponse.EdgeMetadata; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.GetEdges.Request; +import io.openems.common.jsonrpc.type.GetEdges.Response; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class GetEdges implements EndpointRequestType { + + @Override + public String getMethod() { + return "getEdges"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + PaginationOptions paginationOptions // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link GetEdges.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, // + json -> new Request(// + json.getObject("pagination", PaginationOptions.serializer())), // + obj -> JsonUtils.buildJsonObject() // + .add("pagination", PaginationOptions.serializer().serialize(obj.paginationOptions())) // + .build()); + } + + } + + public record Response(// + List edges // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link GetEdges.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Response.class, // + json -> new Response(// + json.getList("edge", EdgeMetadata.serializer())), // + obj -> JsonUtils.buildJsonObject() // + .add("edge", EdgeMetadata.serializer().toListSerializer().serialize(obj.edges())) // + .build()); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/Logout.java b/io.openems.common/src/io/openems/common/jsonrpc/type/Logout.java new file mode 100644 index 00000000000..5462a5ba76e --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/Logout.java @@ -0,0 +1,24 @@ +package io.openems.common.jsonrpc.type; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; + +public class Logout implements EndpointRequestType { + + @Override + public String getMethod() { + return "logout"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesData.java b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesData.java new file mode 100644 index 00000000000..972675bdc64 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesData.java @@ -0,0 +1,197 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableList; + +import java.time.ZoneId; +import java.time.ZoneOffset; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.time.temporal.ChronoUnit; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TimeZone; +import java.util.TreeMap; +import java.util.stream.IntStream; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonElementPath; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserChannelAddress; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesData.Request; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesData.Response; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; + +public class QueryHistoricTimeseriesData implements EndpointRequestType { + + @Override + public String getMethod() { + return "queryHistoricTimeseriesData"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // null-able + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link QueryHistoricTimeseriesData}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, json -> { + final var timezone = json.getObject("timezone", QueryHistoricTimeseriesData.zoneIdSerializer()); + final var resolution = json.getObject("resolution", QueryHistoricTimeseriesData.resolutionSerializer()); + + return new Request(// + json.getLocalDate("fromDate").atStartOfDay(timezone), // + json.getLocalDate("toDate").atStartOfDay(timezone).plusDays(1), // + json.getSet("channels", JsonElementPath::getAsChannelAddress), // + resolution // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("fromDate", obj.fromDate().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .addProperty("toDate", obj.toDate().minusDays(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .add("channels", obj.channels().stream() // + .map(Object::toString) // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) // + .add("timezone", + QueryHistoricTimeseriesData.zoneIdSerializer().serialize(obj.fromDate().getZone())) // + .add("resolution", + QueryHistoricTimeseriesData.resolutionSerializer().serialize(obj.resolution())) // + .build(); + }); + } + + } + + public record Response(SortedMap> table) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link QueryHistoricTimeseriesData.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(QueryHistoricTimeseriesData.Response.class, json -> { + + final var a = json.getJsonObjectPath("data").collect(new StringParserChannelAddress(), + toMap(Entry::getKey, t -> t.getValue().getAsJsonArrayPath().collect(toUnmodifiableList()))); + final var timestamps = json.getList("timestamps", + t -> t.getAsStringPathZonedDateTime(DateTimeFormatter.ISO_INSTANT).get()); + + final var resultMap = IntStream.range(0, timestamps.size()) // + .mapToObj(i -> { + final var timestamp = timestamps.get(i); + final SortedMap b = a.entrySet().stream() // + .collect(toMap(t -> t.getKey().get(), + t -> t.getValue().get(i).getAsJsonPrimitivePath().get(), (t, u) -> u, + TreeMap::new)); + return Map.entry(timestamp, b); + }).collect(toMap(t -> t.getKey(), t -> t.getValue(), (t, u) -> u, TreeMap::new)); + + return new Response(resultMap); + }, obj -> { + final var data = new JsonObject(); + for (final var rowEntry : obj.table().entrySet()) { + for (final var colEntry : rowEntry.getValue().entrySet()) { + var channelAddress = colEntry.getKey().toString(); + var value = colEntry.getValue(); + var channelValuesElement = data.get(channelAddress); + JsonArray channelValues; + if (channelValuesElement != null) { + channelValues = channelValuesElement.getAsJsonArray(); + } else { + channelValues = new JsonArray(); + } + channelValues.add(value); + data.add(channelAddress, channelValues); + } + } + return JsonUtils.buildJsonObject() // + .add("timestamps", obj.table().keySet().stream() // + .map(t -> new JsonPrimitive(t.format(DateTimeFormatter.ISO_INSTANT))) // + .collect(toJsonArray())) + .add("data", data) // + .build(); + }); + } + + } + + /** + * Returns a {@link JsonSerializer} for a {@link QueryHistoricTimeseriesEnergy}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer zoneIdSerializer() { + return jsonSerializer(ZoneId.class, json -> { + return json.multiple(List.of(// + // For UI version before 2022.4.0 + new JsonElementPath.Case<>(JsonElementPath::isNumber, t -> { + return ZoneId.ofOffset("", ZoneOffset.ofTotalSeconds(t.getAsInt() * -1)); + }), // + new JsonElementPath.Case<>(t -> true, t -> { + return TimeZone.getTimeZone(t.getAsString()).toZoneId(); + }) // + )); + }, obj -> { + return new JsonPrimitive(obj.toString()); + }); + } + + /** + * Returns a {@link JsonSerializer} for a {@link QueryHistoricTimeseriesEnergy}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer resolutionSerializer() { + return jsonSerializer(Resolution.class, json -> { + return json.multiple(List.of(// + // For UI version before 2022.4.0 + new JsonElementPath.Case<>(t -> /* t.isPresent() && */t.isJsonPrimitive(), t -> { + return new Resolution(t.getAsInt(), ChronoUnit.SECONDS); + }), // + new JsonElementPath.Case<>(t -> true, t -> { + final var obj = t.getAsJsonObjectPath(); + return new Resolution(obj.getInt("value"), obj.getEnum("unit", ChronoUnit.class)); + }) // + )); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("value", obj.getValue()) // + .addProperty("unit", obj.getUnit()) // + .build(); + }); + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesEnergy.java b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesEnergy.java new file mode 100644 index 00000000000..eec77291992 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesEnergy.java @@ -0,0 +1,100 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static io.openems.common.utils.JsonUtils.toJsonObject; +import static java.util.stream.Collectors.toMap; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Set; + +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonElementPath; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserChannelAddress; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesEnergy.Request; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesEnergy.Response; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; + +public class QueryHistoricTimeseriesEnergy implements EndpointRequestType { + + @Override + public String getMethod() { + return "queryHistoricTimeseriesEnergy"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link QueryHistoricTimeseriesEnergy}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, json -> { + final var timezone = json.getObject("timezone", QueryHistoricTimeseriesData.zoneIdSerializer()); + + return new Request(// + json.getLocalDate("fromDate").atStartOfDay(timezone), // + json.getLocalDate("toDate").atStartOfDay(timezone).plusDays(1), // + json.getSet("channels", JsonElementPath::getAsChannelAddress) // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("fromDate", obj.fromDate().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .addProperty("toDate", obj.toDate().minusDays(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .add("channels", obj.channels().stream() // + .map(Object::toString) // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) // + .add("timezone", + QueryHistoricTimeseriesData.zoneIdSerializer().serialize(obj.fromDate().getZone())) // + .build(); + }); + } + + } + + public record Response(Map data) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link QueryHistoricTimeseriesEnergy.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(QueryHistoricTimeseriesEnergy.Response.class, json -> { + return new Response(json.getJsonObjectPath("data").collect(new StringParserChannelAddress(), + toMap(t -> t.getKey().get(), t -> t.getValue().get()))); + }, obj -> { + return JsonUtils.buildJsonObject() // + .add("data", obj.data().entrySet().stream() // + .collect(toJsonObject(t -> t.getKey().toString(), t -> t.getValue()))) // + .build(); + }); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesEnergyPerPeriod.java b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesEnergyPerPeriod.java new file mode 100644 index 00000000000..c4af273dac7 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesEnergyPerPeriod.java @@ -0,0 +1,145 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonArray; +import static java.util.stream.Collectors.toMap; +import static java.util.stream.Collectors.toUnmodifiableList; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.SortedMap; +import java.util.TreeMap; +import java.util.stream.IntStream; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonElementPath; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserChannelAddress; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesEnergyPerPeriod.Request; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesEnergyPerPeriod.Response; +import io.openems.common.timedata.Resolution; +import io.openems.common.types.ChannelAddress; +import io.openems.common.utils.JsonUtils; + +public class QueryHistoricTimeseriesEnergyPerPeriod implements EndpointRequestType { + + @Override + public String getMethod() { + return "queryHistoricTimeseriesEnergyPerPeriod"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + ZonedDateTime fromDate, // + ZonedDateTime toDate, // + Set channels, // + Resolution resolution // null-able + ) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link QueryHistoricTimeseriesEnergyPerPeriod}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, json -> { + final var timezone = json.getObject("timezone", QueryHistoricTimeseriesData.zoneIdSerializer()); + final var resolution = json.getObject("resolution", QueryHistoricTimeseriesData.resolutionSerializer()); + + return new Request(// + json.getLocalDate("fromDate").atStartOfDay(timezone), // + json.getLocalDate("toDate").atStartOfDay(timezone).plusDays(1), // + json.getSet("channels", JsonElementPath::getAsChannelAddress), // + resolution // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("fromDate", obj.fromDate().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .addProperty("toDate", obj.toDate().minusDays(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .add("channels", + obj.channels().stream().map(Object::toString).map(JsonPrimitive::new) + .collect(toJsonArray())) // + .add("timezone", + QueryHistoricTimeseriesData.zoneIdSerializer().serialize(obj.fromDate().getZone())) // + .add("resolution", + QueryHistoricTimeseriesData.resolutionSerializer().serialize(obj.resolution())) // + .build(); + }); + } + + } + + public record Response(SortedMap> table) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link QueryHistoricTimeseriesEnergyPerPeriod.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(QueryHistoricTimeseriesEnergyPerPeriod.Response.class, json -> { + + final var data = json.getJsonObjectPath("data").collect(new StringParserChannelAddress(), + toMap(Entry::getKey, t -> t.getValue().getAsJsonArrayPath().collect(toUnmodifiableList()))); + final var timestamps = json.getList("timestamps", + t -> t.getAsStringPathZonedDateTime(DateTimeFormatter.ISO_INSTANT).get()); + + final var resultMap = IntStream.range(0, timestamps.size()) // + .mapToObj(i -> { + final var timestamp = timestamps.get(i); + final SortedMap b = data.entrySet().stream() // + .collect(toMap(t -> t.getKey().get(), + t -> t.getValue().get(i).getAsJsonPrimitivePath().get(), (t, u) -> u, + TreeMap::new)); + return Map.entry(timestamp, b); + }).collect(toMap(t -> t.getKey(), t -> t.getValue(), (t, u) -> u, TreeMap::new)); + + return new Response(resultMap); + }, obj -> { + final var data = new JsonObject(); + for (final var rowEntry : obj.table().entrySet()) { + for (final var colEntry : rowEntry.getValue().entrySet()) { + var channelAddress = colEntry.getKey().toString(); + var value = colEntry.getValue(); + var channelValuesElement = data.get(channelAddress); + JsonArray channelValues; + if (channelValuesElement != null) { + channelValues = channelValuesElement.getAsJsonArray(); + } else { + channelValues = new JsonArray(); + } + channelValues.add(value); + data.add(channelAddress, channelValues); + } + } + return JsonUtils.buildJsonObject() // + .add("timestamps", obj.table().keySet().stream() // + .map(t -> new JsonPrimitive(t.format(DateTimeFormatter.ISO_INSTANT))) // + .collect(toJsonArray())) + .add("data", data) // + .build(); + }); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesExportXlxs.java b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesExportXlxs.java new file mode 100644 index 00000000000..a1b51cda0fe --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/QueryHistoricTimeseriesExportXlxs.java @@ -0,0 +1,61 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.QueryHistoricTimeseriesExportXlxs.Request; +import io.openems.common.utils.JsonUtils; + +public class QueryHistoricTimeseriesExportXlxs implements EndpointRequestType { + + @Override + public String getMethod() { + return "queryHistoricTimeseriesExportXlxs"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Base64RequestType.serializer(); + } + + public record Request(// + ZonedDateTime fromDate, // + ZonedDateTime toDate // + ) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link QueryHistoricTimeseriesExportXlxs}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, json -> { + final var timezone = json.getObject("timezone", QueryHistoricTimeseriesData.zoneIdSerializer()); + + return new Request(// + json.getLocalDate("fromDate").atStartOfDay(timezone), // + json.getLocalDate("toDate").atStartOfDay(timezone).plusDays(1) // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("fromDate", obj.fromDate().format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .addProperty("toDate", obj.toDate().minusDays(1).format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .add("timezone", + QueryHistoricTimeseriesData.zoneIdSerializer().serialize(obj.fromDate().getZone())) // + .build(); + }); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/SetChannelValue.java b/io.openems.common/src/io/openems/common/jsonrpc/type/SetChannelValue.java new file mode 100644 index 00000000000..e84f5c67be7 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/SetChannelValue.java @@ -0,0 +1,82 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.Objects; + +import com.google.gson.JsonElement; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.SetChannelValue.Request; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class SetChannelValue implements EndpointRequestType { + + @Override + public String getMethod() { + return "updateComponentConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(// + String componentId, // + String channelId, // + JsonElement value // + ) { + + public Request { + Objects.requireNonNull(componentId); + Objects.requireNonNull(channelId); + Objects.requireNonNull(value); + } + + /** + * Returns a {@link JsonSerializer} for a {@link SetChannelValue.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SetChannelValue.Request.class, // + json -> new SetChannelValue.Request(// + json.getString("componentId"), // + json.getString("channelId"), // + json.getJsonElement("value")), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("componentId", obj.componentId()) // + .addProperty("channelId", obj.channelId()) // + .add("value", obj.value()) // + .build()); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/SubscribeEdges.java b/io.openems.common/src/io/openems/common/jsonrpc/type/SubscribeEdges.java new file mode 100644 index 00000000000..0c1e9fe54cd --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/SubscribeEdges.java @@ -0,0 +1,74 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonArray; + +import java.util.Set; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonElementPath; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.SubscribeEdges.Request; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class SubscribeEdges implements EndpointRequestType { + + @Override + public String getMethod() { + return "subscribeEdges"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(// + Set edges // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link SubscribeEdges.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, // + json -> new Request(// + json.getSet("edges", JsonElementPath::getAsString)), // + obj -> JsonUtils.buildJsonObject() // + .add("edges", obj.edges().stream() // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) // + .build()); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/UpdateComponentConfig.java b/io.openems.common/src/io/openems/common/jsonrpc/type/UpdateComponentConfig.java new file mode 100644 index 00000000000..877c94194ae --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/UpdateComponentConfig.java @@ -0,0 +1,78 @@ +package io.openems.common.jsonrpc.type; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import java.util.List; +import java.util.Objects; + +import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.UpdateComponentConfig.Request; +import io.openems.common.utils.JsonUtils; + +/** + * Represents a JSON-RPC Request for 'createComponentConfig'. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "createComponentConfig",
+ *   "params": {
+ *     "factoryPid": string,
+ *     "properties": [{
+ *       "name": string,
+ *       "value": any
+ *     }]
+ *   }
+ * }
+ * 
+ */ +public class UpdateComponentConfig implements EndpointRequestType { + + @Override + public String getMethod() { + return "updateComponentConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(// + String componentId, // + List properties // + ) { + + public Request { + Objects.requireNonNull(componentId); + Objects.requireNonNull(properties); + } + + /** + * Returns a {@link JsonSerializer} for a {@link UpdateComponentConfig.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(UpdateComponentConfig.Request.class, // + json -> new UpdateComponentConfig.Request(// + json.getString("componentId"), // + json.getList("properties", Property.serializer())), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("componentId", obj.componentId()) // + .add("properties", Property.serializer().toListSerializer().serialize(obj.properties())) // + .build()); + } + + } + +} diff --git a/io.openems.common/src/io/openems/common/jsonrpc/type/package-info.java b/io.openems.common/src/io/openems/common/jsonrpc/type/package-info.java new file mode 100644 index 00000000000..7daf34c5395 --- /dev/null +++ b/io.openems.common/src/io/openems/common/jsonrpc/type/package-info.java @@ -0,0 +1,3 @@ +@org.osgi.annotation.versioning.Version("1.0.0") +@org.osgi.annotation.bundle.Export +package io.openems.common.jsonrpc.type; diff --git a/io.openems.common/src/io/openems/common/utils/FunctionUtils.java b/io.openems.common/src/io/openems/common/utils/FunctionUtils.java index 2b9d3fef218..4e9141d58c0 100644 --- a/io.openems.common/src/io/openems/common/utils/FunctionUtils.java +++ b/io.openems.common/src/io/openems/common/utils/FunctionUtils.java @@ -1,5 +1,6 @@ package io.openems.common.utils; +import java.util.Objects; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Supplier; @@ -96,6 +97,59 @@ public static Supplier supplier(Supplier supplier) { return supplier; } + /** + * Returns a {@link Supplier} that lazily initializes and caches the value from + * the provided supplier. The value is computed and retrieved only once. + * Subsequent calls to {@link Supplier#get()} will return the cached value, + * avoiding recomputation. + *

+ * This implementation is not thread-safe. If multiple threads invoke + * {@link Supplier#get()} concurrently, it may lead to inconsistent behavior, + * such as multiple invocations of the supplier or the value being computed + * multiple times. + *

+ * + *

+ * Example usage: + *

+ * + *
+	 * var lazyStringSupplier = lazySingletion(() -> {
+	 * 	System.out.println("Computing the value...");
+	 * 	return "Hello, World!";
+	 * });
+	 * 
+	 * System.out.println(lazyStringSupplier.get()); // Computes and prints the value
+	 * System.out.println(lazyStringSupplier.get()); // Prints the cached value, no recomputation
+	 * 
+ * + * @param supplier The original supplier that provides the value to be lazily + * computed. + * @param The type of the value that the supplier produces. + * @return A {@link Supplier} that returns the cached value after the first + * computation. + * @throws NullPointerException if the provided supplier is {@code null}. + * + * @see Supplier + */ + public static Supplier lazySingleton(Supplier supplier) { + Objects.requireNonNull(supplier); + return new Supplier() { + + private boolean isInitialized = false; + private T value; + + @Override + public T get() { + if (!this.isInitialized) { + this.value = supplier.get(); + this.isInitialized = true; + } + return this.value; + } + }; + } + private FunctionUtils() { } diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/BooleanPathActualTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/BooleanPathActualTest.java new file mode 100644 index 00000000000..1dcdb1bdf3c --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/BooleanPathActualTest.java @@ -0,0 +1,85 @@ +package io.openems.common.jsonrpc.serialization; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.utils.JsonUtils; + +public class BooleanPathActualTest { + + @Test + public void testFromJsonObjectPathActual() { + final JsonObjectPath json = new JsonObjectPathActual.JsonObjectPathActualNonNull(JsonUtils.buildJsonObject() // + .addProperty("value", false) // + .addProperty("nonBooleanValue", 99) // + .build()); + + assertFalse(json.getBoolean("value")); + assertFalse(json.getBooleanNullable("value")); + + assertThrows(RuntimeException.class, () -> { + json.getBoolean("someOtherValue"); + }); + assertNull(json.getBooleanNullable("someOtherValue")); + + assertThrows(RuntimeException.class, () -> { + json.getBoolean("nonBooleanValue"); + }); + assertThrows(RuntimeException.class, () -> { + json.getBooleanNullable("nonBooleanValue"); + }); + } + + @Test + public void testFromJsonElementPathActualSuccess() { + final JsonElementPath path = new JsonElementPathActual.JsonElementPathActualNonNull(new JsonPrimitive(true)); + + assertTrue(path.getAsBoolean()); + assertTrue(path.getAsBooleanPath().get()); + } + + @Test + public void testFromJsonElementPathActualFail() { + final JsonElementPath path = new JsonElementPathActual.JsonElementPathActualNonNull( + new JsonPrimitive("string")); + + assertThrows(RuntimeException.class, () -> { + path.getAsBoolean(); + }); + assertThrows(RuntimeException.class, () -> { + path.getAsBooleanPath(); + }); + } + + @Test + public void testNonNull() { + final BooleanPath booleanPath = new BooleanPathActual.BooleanPathActualNonNull(false); + + assertFalse(booleanPath.get()); + } + + @Test + public void testNullableNonNull() { + final BooleanPathNullable booleanPath = new BooleanPathActual.BooleanPathActualNullable(false); + + assertFalse(booleanPath.getOrNull()); + assertFalse(booleanPath.getOrDefault(true)); + assertFalse(booleanPath.getOptional().get()); + } + + @Test + public void testNullableNull() { + final BooleanPathNullable booleanPath = new BooleanPathActual.BooleanPathActualNullable(null); + + assertNull(booleanPath.getOrNull()); + assertTrue(booleanPath.getOrDefault(true)); + assertTrue(booleanPath.getOptional().isEmpty()); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/BooleanPathDummyTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/BooleanPathDummyTest.java new file mode 100644 index 00000000000..874a6c5940a --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/BooleanPathDummyTest.java @@ -0,0 +1,46 @@ +package io.openems.common.jsonrpc.serialization; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +public class BooleanPathDummyTest { + + @Test + public void testFromJsonObjectPathDummy() { + final JsonObjectPath json = new JsonObjectPathDummy.JsonObjectPathDummyNonNull(); + + assertNotNull(json.getBoolean("value")); + assertNull(json.getBooleanNullable("otherValue")); + } + + @Test + public void testFromJsonElementPathActualSuccess() { + final JsonElementPath path = new JsonElementPathDummy.JsonElementPathDummyNonNull(); + + assertNotNull(path.getAsBoolean()); + assertNotNull(path.getAsBooleanPath()); + } + + @Test + public void testNonNull() { + final var booleanPath = new BooleanPathDummy.BooleanPathDummyNonNull(); + + assertFalse(booleanPath.get()); + assertNotNull(booleanPath.buildPath()); + } + + @Test + public void testNullable() { + final var booleanPath = new BooleanPathDummy.BooleanPathDummyNullable(); + + assertNull(booleanPath.getOrNull()); + assertTrue(booleanPath.getOrDefault(true)); + assertTrue(booleanPath.getOptional().isEmpty()); + assertNotNull(booleanPath.buildPath()); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonArrayPathActualTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonArrayPathActualTest.java new file mode 100644 index 00000000000..a542a3a2959 --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonArrayPathActualTest.java @@ -0,0 +1,32 @@ +package io.openems.common.jsonrpc.serialization; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.stringSerializer; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import org.junit.Test; + +import io.openems.common.utils.JsonUtils; + +public class JsonArrayPathActualTest { + + @Test + public void testPredefinedCollectors() throws Exception { + final JsonArrayPath jsonPath = new JsonArrayPathActual.JsonArrayPathActualNonNull(JsonUtils.buildJsonArray() // + .add("someValue") // + .build()); + + final var list = jsonPath.getAsList(stringSerializer()); + assertEquals(1, list.size()); + assertEquals("someValue", list.get(0)); + + final var array = jsonPath.getAsArray(String[]::new, stringSerializer()); + assertEquals(1, array.length); + assertEquals("someValue", array[0]); + + final var set = jsonPath.getAsSet(stringSerializer()); + assertEquals(1, set.size()); + assertTrue(set.contains("someValue")); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonObjectPathActualTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonObjectPathActualTest.java new file mode 100644 index 00000000000..33da1f16ba3 --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonObjectPathActualTest.java @@ -0,0 +1,46 @@ +package io.openems.common.jsonrpc.serialization; + +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; + +import org.junit.Test; + +import io.openems.common.utils.JsonUtils; + +public class JsonObjectPathActualTest { + + @Test + public void testJsonObjectPathActualNonNull() throws Exception { + assertThrows(RuntimeException.class, () -> { + new JsonObjectPathActual.JsonObjectPathActualNonNull(null); + }); + final var jsonPath = new JsonObjectPathActual.JsonObjectPathActualNonNull(JsonUtils.buildJsonObject() // + .addProperty("value", false) // + .build()); + + assertNotNull(jsonPath.getJsonElementPath("value")); + assertThrows(RuntimeException.class, () -> { + jsonPath.getJsonElementPath("notExistingValue"); + }); + assertNotNull(jsonPath.getNullableJsonElementPath("value")); + assertNotNull(jsonPath.getNullableJsonElementPath("notExistingValue")); + } + + @Test + public void testJsonObjectPathActualNullableNonNull() throws Exception { + final var jsonPath = new JsonObjectPathActual.JsonObjectPathActualNullable(JsonUtils.buildJsonObject() // + .addProperty("value", false) // + .build()); + + assertNotNull(jsonPath.getOrNull()); + } + + @Test + public void testJsonObjectPathActualNullableNull() throws Exception { + final var jsonPath = new JsonObjectPathActual.JsonObjectPathActualNullable(null); + + assertNull(jsonPath.getOrNull()); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonObjectPathTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonObjectPathTest.java new file mode 100644 index 00000000000..823e541e699 --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonObjectPathTest.java @@ -0,0 +1,2351 @@ +package io.openems.common.jsonrpc.serialization; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.stringSerializer; +import static java.util.stream.Collectors.toMap; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.format.DateTimeFormatter; +import java.util.Map.Entry; +import java.util.Optional; +import java.util.UUID; + +import org.junit.Before; +import org.junit.Test; + +import com.google.gson.JsonNull; + +import io.openems.common.channel.Level; +import io.openems.common.jsonrpc.serialization.StringPathParser.StringParserString; +import io.openems.common.types.ChannelAddress; +import io.openems.common.types.SemanticVersion; +import io.openems.common.utils.JsonUtils; + +public class JsonObjectPathTest { + + private JsonObjectPath path; + + private final ChannelAddress channelAddress = new ChannelAddress("component0", "Channel"); + private final SemanticVersion semanticVersion = SemanticVersion.fromString("2020.1.1"); + private final LocalDate localDate = LocalDate.of(2025, 1, 1); + private final ZonedDateTime zonedDateTime = ZonedDateTime.of(LocalDateTime.of(2025, 1, 1, 1, 1), ZoneId.of("UTC")); + private final UUID uuid = UUID.randomUUID(); + + @Before + public void before() { + this.path = new JsonObjectPathActual.JsonObjectPathActualNonNull(JsonUtils.buildJsonObject() // + // string values + .addProperty("string", "string") // + .addProperty("channelAddress", this.channelAddress.toString()) // + .addProperty("semanticVersion", this.semanticVersion.toString()) // + .addProperty("enum", Level.WARNING) // + .addProperty("localDate", this.localDate.format(DateTimeFormatter.ISO_LOCAL_DATE)) // + .addProperty("zonedDateTime", this.zonedDateTime.format(DateTimeFormatter.ISO_ZONED_DATE_TIME)) // + .addProperty("uuid", this.uuid.toString()) // + // number values + .addProperty("number", 10) // + .addProperty("long", 10L) // + .addProperty("int", 10) // + .addProperty("double", 10.5D) // + .addProperty("float", 10.5F) // + // boolean values + .addProperty("boolean", true) // + // array values + .add("array", JsonUtils.buildJsonArray() // + .add("string") // + .build()) + // object values + .add("object", JsonUtils.buildJsonObject() // + .addProperty("string", "string") // + .build()) + // null + .add("null", JsonNull.INSTANCE) // + .build()); + } + + @Test + public void testGetJsonElementPath() { + assertNotNull(this.path.getJsonElementPath("string")); + assertNotNull(this.path.getJsonElementPath("number")); + assertNotNull(this.path.getJsonElementPath("boolean")); + assertNotNull(this.path.getJsonElementPath("array")); + assertNotNull(this.path.getJsonElementPath("object")); + assertNotNull(this.path.getJsonElementPath("null")); + + assertThrows(RuntimeException.class, () -> { + this.path.getJsonElementPath("notExisting"); + }); + } + + @Test + public void testGetJsonElement() { + assertNotNull(this.path.getJsonElement("string")); + assertNotNull(this.path.getJsonElement("number")); + assertNotNull(this.path.getJsonElement("boolean")); + assertNotNull(this.path.getJsonElement("array")); + assertNotNull(this.path.getJsonElement("object")); + assertNotNull(this.path.getJsonElement("null")); + + assertThrows(RuntimeException.class, () -> { + this.path.getJsonElement("notExisting"); + }); + } + + @Test + public void testGetJsonPrimitivePath() { + assertNotNull(this.path.getJsonPrimitivePath("string")); + assertNotNull(this.path.getJsonPrimitivePath("number")); + assertNotNull(this.path.getJsonPrimitivePath("boolean")); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitivePath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitivePath("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitivePath("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitivePath("notExisting"); + }); + } + + @Test + public void testGetJsonPrimitive() { + assertNotNull(this.path.getJsonPrimitive("string")); + assertNotNull(this.path.getJsonPrimitive("number")); + assertNotNull(this.path.getJsonPrimitive("boolean")); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitive("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitive("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitive("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonPrimitive("notExisting"); + }); + } + + @Test + public void testGetNullableJsonElementPath() { + assertNotNull(this.path.getNullableJsonElementPath("string")); + assertNotNull(this.path.getNullableJsonElementPath("number")); + assertNotNull(this.path.getNullableJsonElementPath("boolean")); + assertNotNull(this.path.getNullableJsonElementPath("array")); + assertNotNull(this.path.getNullableJsonElementPath("object")); + assertNotNull(this.path.getNullableJsonElementPath("null")); + assertNotNull(this.path.getNullableJsonElementPath("notExisting")); + } + + @Test + public void testGetNullableJsonPrimitivePath() { + assertNotNull(this.path.getNullableJsonPrimitivePath("string")); + assertNotNull(this.path.getNullableJsonPrimitivePath("number")); + assertNotNull(this.path.getNullableJsonPrimitivePath("boolean")); + assertNotNull(this.path.getNullableJsonPrimitivePath("null")); + assertNotNull(this.path.getNullableJsonPrimitivePath("notExisting")); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableJsonPrimitivePath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableJsonPrimitivePath("object"); + }); + } + + @Test + public void testCollect() { + final var result = this.path.collect(new StringPathParser.StringParserString(), + toMap(t -> t.getKey().get(), t -> t.getValue().get())); + + assertEquals(this.path.get().size(), result.size()); + } + + @Test + public void testCollectStringKeys() { + final var result = this.path.collectStringKeys(toMap(Entry::getKey, t -> t.getValue().get())); + + assertEquals(this.path.get().size(), result.size()); + } + + @Test + public void testGetStringPathString() { + assertNotNull(this.path.getStringPath("string")); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("notExisting"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableJsonPrimitivePath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableJsonPrimitivePath("object"); + }); + } + + @Test + public void testGetStringPathStringStringParserOfT() { + assertNotNull(this.path.getStringPath("string", new StringPathParser.StringParserString())); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("number", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("boolean", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("null", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("notExisting", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("array", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPath("object", new StringPathParser.StringParserString()); + }); + } + + @Test + public void testGetStringParsed() { + assertEquals("string", this.path.getStringParsed("string", new StringPathParser.StringParserString())); + assertEquals(Level.WARNING, + this.path.getStringParsed("enum", new StringPathParser.StringParserEnum<>(Level.class))); + assertEquals(this.localDate, + this.path.getStringParsed("localDate", new StringPathParser.StringParserLocalDate())); + assertEquals(this.zonedDateTime, + this.path.getStringParsed("zonedDateTime", new StringPathParser.StringParserZonedDateTime())); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsed("number", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsed("boolean", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsed("null", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsed("notExisting", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsed("array", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsed("object", new StringPathParser.StringParserString()); + }); + } + + @Test + public void testGetStringParsedOrNull() { + assertEquals("string", this.path.getStringParsedOrNull("string", new StringPathParser.StringParserString())); + assertEquals(Level.WARNING, + this.path.getStringParsedOrNull("enum", new StringPathParser.StringParserEnum<>(Level.class))); + assertEquals(this.localDate, + this.path.getStringParsedOrNull("localDate", new StringPathParser.StringParserLocalDate())); + assertEquals(this.zonedDateTime, + this.path.getStringParsedOrNull("zonedDateTime", new StringPathParser.StringParserZonedDateTime())); + + assertNull(this.path.getStringParsedOrNull("null", new StringPathParser.StringParserString())); + assertNull(this.path.getStringParsedOrNull("notExisting", new StringPathParser.StringParserString())); + assertNull(this.path.getStringParsedOrNull("null", new StringPathParser.StringParserEnum<>(Level.class))); + assertNull( + this.path.getStringParsedOrNull("notExisting", new StringPathParser.StringParserEnum<>(Level.class))); + assertNull(this.path.getStringParsedOrNull("null", new StringPathParser.StringParserLocalDate())); + assertNull(this.path.getStringParsedOrNull("notExisting", new StringPathParser.StringParserLocalDate())); + assertNull(this.path.getStringParsedOrNull("null", new StringPathParser.StringParserZonedDateTime())); + assertNull(this.path.getStringParsedOrNull("notExisting", new StringPathParser.StringParserZonedDateTime())); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsedOrNull("number", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsedOrNull("boolean", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsedOrNull("array", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringParsedOrNull("object", new StringPathParser.StringParserString()); + }); + } + + @Test + public void testGetOptionalStringParsed() { + assertEquals(Optional.of("string"), + this.path.getOptionalStringParsed("string", new StringPathParser.StringParserString())); + assertEquals(Optional.of(Level.WARNING), + this.path.getOptionalStringParsed("enum", new StringPathParser.StringParserEnum<>(Level.class))); + assertEquals(Optional.of(LocalDate.of(2025, 1, 1)), + this.path.getOptionalStringParsed("localDate", new StringPathParser.StringParserLocalDate())); + assertEquals(Optional.of(ZonedDateTime.of(LocalDateTime.of(2025, 1, 1, 1, 1), ZoneId.of("UTC"))), + this.path.getOptionalStringParsed("zonedDateTime", new StringPathParser.StringParserZonedDateTime())); + + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("null", new StringPathParser.StringParserString())); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("notExisting", new StringPathParser.StringParserString())); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("null", new StringPathParser.StringParserEnum<>(Level.class))); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("notExisting", new StringPathParser.StringParserEnum<>(Level.class))); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("null", new StringPathParser.StringParserLocalDate())); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("notExisting", new StringPathParser.StringParserLocalDate())); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("null", new StringPathParser.StringParserZonedDateTime())); + assertEquals(Optional.empty(), + this.path.getOptionalStringParsed("notExisting", new StringPathParser.StringParserZonedDateTime())); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalStringParsed("number", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalStringParsed("boolean", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalStringParsed("array", new StringPathParser.StringParserString()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalStringParsed("object", new StringPathParser.StringParserString()); + }); + } + + @Test + public void testGetNullableStringPathString() { + assertNotNull(this.path.getNullableStringPathString("string")); + assertNotNull(this.path.getNullableStringPathString("null")); + assertNotNull(this.path.getNullableStringPathString("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathString("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathString("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathString("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathString("object"); + }); + } + + @Test + public void testGetNullableStringPathChannelAddress() { + assertNotNull(this.path.getNullableStringPathChannelAddress("string")); + assertNotNull(this.path.getNullableStringPathChannelAddress("channelAddress")); + assertNotNull(this.path.getNullableStringPathChannelAddress("null")); + assertNotNull(this.path.getNullableStringPathChannelAddress("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathChannelAddress("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathChannelAddress("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathChannelAddress("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathChannelAddress("object"); + }); + } + + @Test + public void testGetNullableStringPathEnum() { + assertNotNull(this.path.getNullableStringPathEnum("string", Level.class)); + assertNotNull(this.path.getNullableStringPathEnum("enum", Level.class)); + assertNotNull(this.path.getNullableStringPathEnum("null", Level.class)); + assertNotNull(this.path.getNullableStringPathEnum("notExisting", Level.class)); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathEnum("number", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathEnum("boolean", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathEnum("array", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathEnum("object", Level.class); + }); + } + + @Test + public void testGetNullableStringPathLocalDateString() { + assertNotNull(this.path.getNullableStringPathLocalDate("string")); + assertNotNull(this.path.getNullableStringPathLocalDate("localDate")); + assertNotNull(this.path.getNullableStringPathLocalDate("null")); + assertNotNull(this.path.getNullableStringPathLocalDate("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("object"); + }); + } + + @Test + public void testGetNullableStringPathLocalDateStringDateTimeFormatter() { + assertNotNull(this.path.getNullableStringPathLocalDate("string", DateTimeFormatter.ISO_LOCAL_DATE)); + assertNotNull(this.path.getNullableStringPathLocalDate("localDate", DateTimeFormatter.ISO_LOCAL_DATE)); + assertNotNull(this.path.getNullableStringPathLocalDate("null", DateTimeFormatter.ISO_LOCAL_DATE)); + assertNotNull(this.path.getNullableStringPathLocalDate("notExisting", DateTimeFormatter.ISO_LOCAL_DATE)); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("number", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("boolean", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("array", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathLocalDate("object", DateTimeFormatter.ISO_LOCAL_DATE); + }); + } + + @Test + public void testGetNullableStringPathSemanticVersion() { + assertNotNull(this.path.getNullableStringPathSemanticVersion("string")); + assertNotNull(this.path.getNullableStringPathSemanticVersion("localDate")); + assertNotNull(this.path.getNullableStringPathSemanticVersion("null")); + assertNotNull(this.path.getNullableStringPathSemanticVersion("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("object"); + }); + } + + @Test + public void testGetNullableStringPath() { + assertNotNull(this.path.getNullableStringPath("string", new StringParserString())); + assertNotNull(this.path.getNullableStringPath("null", new StringParserString())); + assertNotNull(this.path.getNullableStringPath("notExisting", new StringParserString())); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathSemanticVersion("object"); + }); + } + + @Test + public void testGetNullableStringPathUuid() { + assertNotNull(this.path.getNullableStringPathUuid("string")); + assertNotNull(this.path.getNullableStringPathUuid("uuid")); + assertNotNull(this.path.getNullableStringPathUuid("null")); + assertNotNull(this.path.getNullableStringPathUuid("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathUuid("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathUuid("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathUuid("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathUuid("object"); + }); + } + + @Test + public void testGetNullableStringPathZonedDateTimeString() { + assertNotNull(this.path.getNullableStringPathZonedDateTime("string")); + assertNotNull(this.path.getNullableStringPathZonedDateTime("zonedDateTime")); + assertNotNull(this.path.getNullableStringPathZonedDateTime("null")); + assertNotNull(this.path.getNullableStringPathZonedDateTime("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("object"); + }); + } + + @Test + public void testGetNullableStringPathZonedDateTimeStringDateTimeFormatter() { + assertNotNull(this.path.getNullableStringPathZonedDateTime("string", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertNotNull( + this.path.getNullableStringPathZonedDateTime("zonedDateTime", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertNotNull(this.path.getNullableStringPathZonedDateTime("null", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertNotNull( + this.path.getNullableStringPathZonedDateTime("notExisting", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("number", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("boolean", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("array", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableStringPathZonedDateTime("object", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + } + + @Test + public void testGetStringPathUuid() { + assertNotNull(this.path.getStringPathUuid("string")); + assertNotNull(this.path.getStringPathUuid("uuid")); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathUuid("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathUuid("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathUuid("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathUuid("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathUuid("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathUuid("notExisting"); + }); + } + + @Test + public void testGetStringPathSemanticVersion() { + assertNotNull(this.path.getStringPathSemanticVersion("string")); + assertNotNull(this.path.getStringPathSemanticVersion("semanticVersion")); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathSemanticVersion("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathSemanticVersion("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathSemanticVersion("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathSemanticVersion("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathSemanticVersion("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathSemanticVersion("notExisting"); + }); + } + + @Test + public void testGetStringPathEnum() { + assertNotNull(this.path.getStringPathEnum("string", Level.class)); + assertNotNull(this.path.getStringPathEnum("semanticVersion", Level.class)); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathEnum("number", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathEnum("boolean", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathEnum("array", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathEnum("object", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathEnum("null", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathEnum("notExisting", Level.class); + }); + } + + @Test + public void testGetStringPathZonedDateTimeStringDateTimeFormatter() { + assertNotNull(this.path.getStringPathZonedDateTime("string", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertNotNull(this.path.getStringPathZonedDateTime("zonedDateTime", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("number", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("boolean", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("array", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("object", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("null", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("notExisting", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + } + + @Test + public void testGetStringPathZonedDateTimeString() { + assertNotNull(this.path.getStringPathZonedDateTime("string")); + assertNotNull(this.path.getStringPathZonedDateTime("zonedDateTime")); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathZonedDateTime("notExisting"); + }); + } + + @Test + public void testGetStringPathLocalDateStringDateTimeFormatter() { + assertNotNull(this.path.getStringPathLocalDate("string", DateTimeFormatter.ISO_LOCAL_DATE)); + assertNotNull(this.path.getStringPathLocalDate("localDate", DateTimeFormatter.ISO_LOCAL_DATE)); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("number", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("boolean", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("array", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("object", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("null", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("notExisting", DateTimeFormatter.ISO_LOCAL_DATE); + }); + } + + @Test + public void testGetStringPathLocalDateString() { + assertNotNull(this.path.getStringPathLocalDate("string")); + assertNotNull(this.path.getStringPathLocalDate("localDate")); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringPathLocalDate("notExisting"); + }); + } + + @Test + public void testGetNumberPath() { + assertNotNull(this.path.getNumberPath("number")); + assertNotNull(this.path.getNumberPath("long")); + assertNotNull(this.path.getNumberPath("int")); + assertNotNull(this.path.getNumberPath("double")); + assertNotNull(this.path.getNumberPath("float")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNumberPath("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNumberPath("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNumberPath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNumberPath("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNumberPath("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNumberPath("notExisting"); + }); + } + + @Test + public void testGetNullableNumberPath() { + assertNotNull(this.path.getNullableNumberPath("number")); + assertNotNull(this.path.getNullableNumberPath("long")); + assertNotNull(this.path.getNullableNumberPath("int")); + assertNotNull(this.path.getNullableNumberPath("double")); + assertNotNull(this.path.getNullableNumberPath("float")); + assertNotNull(this.path.getNullableNumberPath("null")); + assertNotNull(this.path.getNullableNumberPath("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getNullableNumberPath("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableNumberPath("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableNumberPath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getNullableNumberPath("object"); + }); + } + + @Test + public void testGetString() { + assertEquals("string", this.path.getString("string")); + + assertThrows(RuntimeException.class, () -> { + this.path.getString("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getString("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getString("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getString("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getString("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getString("notExisting"); + }); + } + + @Test + public void testGetStringOrNull() { + assertEquals("string", this.path.getStringOrNull("string")); + assertNull(this.path.getStringOrNull("null")); + assertNull(this.path.getStringOrNull("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getStringOrNull("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringOrNull("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringOrNull("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getStringOrNull("object"); + }); + } + + @Test + public void testGetOptionalString() { + assertEquals(Optional.of("string"), this.path.getOptionalString("string")); + assertEquals(Optional.empty(), this.path.getOptionalString("null")); + assertEquals(Optional.empty(), this.path.getOptionalString("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalString("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalString("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalString("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalString("object"); + }); + } + + @Test + public void testGetUuid() { + assertEquals(this.uuid, this.path.getUuid("uuid")); + + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuid("notExisting"); + }); + } + + @Test + public void testGetUuidOrNull() { + assertEquals(this.uuid, this.path.getUuidOrNull("uuid")); + assertNull(this.path.getUuidOrNull("null")); + assertNull(this.path.getUuidOrNull("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getUuidOrNull("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuidOrNull("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuidOrNull("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuidOrNull("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getUuidOrNull("object"); + }); + } + + @Test + public void testGetOptionalUuid() { + assertEquals(Optional.of(this.uuid), this.path.getOptionalUuid("uuid")); + assertEquals(Optional.empty(), this.path.getOptionalUuid("null")); + assertEquals(Optional.empty(), this.path.getOptionalUuid("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalUuid("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalUuid("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalUuid("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalUuid("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalUuid("object"); + }); + } + + @Test + public void testGetSemanticVersion() { + assertEquals(this.semanticVersion, this.path.getSemanticVersion("semanticVersion")); + + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersion("notExisting"); + }); + } + + @Test + public void testGetSemanticVersionOrNull() { + assertEquals(this.semanticVersion, this.path.getSemanticVersionOrNull("semanticVersion")); + assertNull(this.path.getSemanticVersionOrNull("null")); + assertNull(this.path.getSemanticVersionOrNull("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersionOrNull("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersionOrNull("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersionOrNull("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersionOrNull("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSemanticVersionOrNull("object"); + }); + } + + @Test + public void testGetOptionalSemanticVersion() { + assertEquals(Optional.of(this.semanticVersion), this.path.getOptionalSemanticVersion("semanticVersion")); + assertEquals(Optional.empty(), this.path.getOptionalSemanticVersion("null")); + assertEquals(Optional.empty(), this.path.getOptionalSemanticVersion("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSemanticVersion("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSemanticVersion("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSemanticVersion("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSemanticVersion("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSemanticVersion("object"); + }); + } + + @Test + public void testGetEnum() { + assertEquals(Level.WARNING, this.path.getEnum("enum", Level.class)); + + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("string", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("number", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("boolean", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("array", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("object", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("null", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnum("notExisting", Level.class); + }); + } + + @Test + public void testGetEnumOrNull() { + assertEquals(Level.WARNING, this.path.getEnumOrNull("enum", Level.class)); + assertNull(this.path.getEnumOrNull("null", Level.class)); + assertNull(this.path.getEnumOrNull("notExisting", Level.class)); + + assertThrows(RuntimeException.class, () -> { + this.path.getEnumOrNull("string", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnumOrNull("number", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnumOrNull("boolean", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnumOrNull("array", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getEnumOrNull("object", Level.class); + }); + } + + @Test + public void testGetOptionalEnum() { + assertEquals(Optional.of(Level.WARNING), this.path.getOptionalEnum("enum", Level.class)); + assertEquals(Optional.empty(), this.path.getOptionalEnum("null", Level.class)); + assertEquals(Optional.empty(), this.path.getOptionalEnum("notExisting", Level.class)); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalEnum("string", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalEnum("number", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalEnum("boolean", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalEnum("array", Level.class); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalEnum("object", Level.class); + }); + } + + @Test + public void testGetZonedDateTimeString() { + assertEquals(this.zonedDateTime, this.path.getZonedDateTime("zonedDateTime")); + + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("notExisting"); + }); + } + + @Test + public void testGetZonedDateTimeStringDateTimeFormatter() { + assertEquals(this.zonedDateTime, + this.path.getZonedDateTime("zonedDateTime", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("string", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("number", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("boolean", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("array", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("object", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("null", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTime("notExisting", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + } + + @Test + public void testGetZonedDateTimeOrNullString() { + assertEquals(this.zonedDateTime, this.path.getZonedDateTimeOrNull("zonedDateTime")); + assertNull(this.path.getZonedDateTimeOrNull("null")); + assertNull(this.path.getZonedDateTimeOrNull("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("object"); + }); + } + + @Test + public void testGetZonedDateTimeOrNullStringDateTimeFormatter() { + assertEquals(this.zonedDateTime, + this.path.getZonedDateTimeOrNull("zonedDateTime", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertNull(this.path.getZonedDateTimeOrNull("null", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertNull(this.path.getZonedDateTimeOrNull("notExisting", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("string", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("number", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("boolean", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("array", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getZonedDateTimeOrNull("object", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + } + + @Test + public void testGetOptionalZonedDateTimeString() { + assertEquals(Optional.of(this.zonedDateTime), this.path.getOptionalZonedDateTime("zonedDateTime")); + assertEquals(Optional.empty(), this.path.getOptionalZonedDateTime("null")); + assertEquals(Optional.empty(), this.path.getOptionalZonedDateTime("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("object"); + }); + } + + @Test + public void testGetOptionalZonedDateTimeStringDateTimeFormatter() { + assertEquals(Optional.of(this.zonedDateTime), + this.path.getOptionalZonedDateTime("zonedDateTime", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertEquals(Optional.empty(), + this.path.getOptionalZonedDateTime("null", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + assertEquals(Optional.empty(), + this.path.getOptionalZonedDateTime("notExisting", DateTimeFormatter.ISO_ZONED_DATE_TIME)); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("string", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("number", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("boolean", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("array", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalZonedDateTime("object", DateTimeFormatter.ISO_ZONED_DATE_TIME); + }); + } + + @Test + public void testGetLocalDateString() { + assertEquals(this.localDate, this.path.getLocalDate("localDate")); + + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("notExisting"); + }); + } + + @Test + public void testGetLocalDateStringDateTimeFormatter() { + assertEquals(this.localDate, this.path.getLocalDate("localDate", DateTimeFormatter.ISO_LOCAL_DATE)); + + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("string", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("number", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("boolean", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("array", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("object", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("null", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDate("notExisting", DateTimeFormatter.ISO_LOCAL_DATE); + }); + } + + @Test + public void testGetLocalDateOrNullString() { + assertEquals(this.localDate, this.path.getLocalDateOrNull("localDate")); + assertNull(this.path.getLocalDateOrNull("null")); + assertNull(this.path.getLocalDateOrNull("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("object"); + }); + } + + @Test + public void testGetLocalDateOrNullStringDateTimeFormatter() { + assertEquals(this.localDate, this.path.getLocalDateOrNull("localDate", DateTimeFormatter.ISO_LOCAL_DATE)); + assertNull(this.path.getLocalDateOrNull("null", DateTimeFormatter.ISO_LOCAL_DATE)); + assertNull(this.path.getLocalDateOrNull("notExisting", DateTimeFormatter.ISO_LOCAL_DATE)); + + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("string", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("number", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("boolean", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("array", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLocalDateOrNull("object", DateTimeFormatter.ISO_LOCAL_DATE); + }); + } + + @Test + public void testGetOptionalLocalDateString() { + assertEquals(Optional.of(this.localDate), this.path.getOptionalLocalDate("localDate")); + assertEquals(Optional.empty(), this.path.getOptionalLocalDate("null")); + assertEquals(Optional.empty(), this.path.getOptionalLocalDate("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("object"); + }); + } + + @Test + public void testGetOptionalLocalDateStringDateTimeFormatter() { + assertEquals(Optional.of(this.localDate), + this.path.getOptionalLocalDate("localDate", DateTimeFormatter.ISO_LOCAL_DATE)); + assertEquals(Optional.empty(), this.path.getOptionalLocalDate("null", DateTimeFormatter.ISO_LOCAL_DATE)); + assertEquals(Optional.empty(), this.path.getOptionalLocalDate("notExisting", DateTimeFormatter.ISO_LOCAL_DATE)); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("string", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("number", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("boolean", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("array", DateTimeFormatter.ISO_LOCAL_DATE); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLocalDate("object", DateTimeFormatter.ISO_LOCAL_DATE); + }); + } + + @Test + public void testGetDouble() { + assertEquals(10.5D, this.path.getDouble("double"), 0); + assertEquals(10.5D, this.path.getDouble("float"), 0); + assertEquals(10D, this.path.getDouble("long"), 0); + assertEquals(10D, this.path.getDouble("int"), 0); + + assertThrows(RuntimeException.class, () -> { + this.path.getDouble("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDouble("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDouble("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDouble("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDouble("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDouble("notExisting"); + }); + } + + @Test + public void testGetDoubleOrDefault() { + assertEquals(10.5D, this.path.getDoubleOrDefault("double", 0), 0); + assertEquals(10.5D, this.path.getDoubleOrDefault("float", 0), 0); + assertEquals(10D, this.path.getDoubleOrDefault("long", 0), 0); + assertEquals(10D, this.path.getDoubleOrDefault("int", 0), 0); + assertEquals(0, this.path.getDoubleOrDefault("null", 0), 0); + assertEquals(0, this.path.getDoubleOrDefault("notExisting", 0), 0); + + assertThrows(RuntimeException.class, () -> { + this.path.getDoubleOrDefault("string", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDoubleOrDefault("boolean", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDoubleOrDefault("array", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getDoubleOrDefault("object", 0); + }); + } + + @Test + public void testGetOptionalDouble() { + assertEquals(Optional.of(10.5D), this.path.getOptionalDouble("double")); + assertEquals(Optional.of(10.5D), this.path.getOptionalDouble("float")); + assertEquals(Optional.of(10D), this.path.getOptionalDouble("long")); + assertEquals(Optional.of(10D), this.path.getOptionalDouble("int")); + assertEquals(Optional.empty(), this.path.getOptionalDouble("null")); + assertEquals(Optional.empty(), this.path.getOptionalDouble("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalDouble("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalDouble("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalDouble("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalDouble("object"); + }); + } + + @Test + public void testGetFloat() { + assertEquals(10.5F, this.path.getFloat("double"), 0); + assertEquals(10.5F, this.path.getFloat("float"), 0); + assertEquals(10F, this.path.getFloat("long"), 0); + assertEquals(10F, this.path.getFloat("int"), 0); + + assertThrows(RuntimeException.class, () -> { + this.path.getFloat("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloat("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloat("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloat("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloat("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloat("notExisting"); + }); + } + + @Test + public void testGetFloatOrDefault() { + assertEquals(10.5F, this.path.getFloatOrDefault("double", 0), 0); + assertEquals(10.5F, this.path.getFloatOrDefault("float", 0), 0); + assertEquals(10F, this.path.getFloatOrDefault("long", 0), 0); + assertEquals(10F, this.path.getFloatOrDefault("int", 0), 0); + assertEquals(0F, this.path.getFloatOrDefault("null", 0), 0); + assertEquals(0F, this.path.getFloatOrDefault("notExisting", 0), 0); + + assertThrows(RuntimeException.class, () -> { + this.path.getFloatOrDefault("string", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloatOrDefault("boolean", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloatOrDefault("array", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getFloatOrDefault("object", 0); + }); + } + + @Test + public void testGetOptionalFloat() { + assertEquals(Optional.of(10.5F), this.path.getOptionalFloat("double")); + assertEquals(Optional.of(10.5F), this.path.getOptionalFloat("float")); + assertEquals(Optional.of(10F), this.path.getOptionalFloat("long")); + assertEquals(Optional.of(10F), this.path.getOptionalFloat("int")); + assertEquals(Optional.empty(), this.path.getOptionalFloat("null")); + assertEquals(Optional.empty(), this.path.getOptionalFloat("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalFloat("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalFloat("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalFloat("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalFloat("object"); + }); + } + + @Test + public void testGetLong() { + assertEquals(10L, this.path.getLong("double")); + assertEquals(10L, this.path.getLong("float")); + assertEquals(10L, this.path.getLong("long")); + assertEquals(10L, this.path.getLong("int")); + + assertThrows(RuntimeException.class, () -> { + this.path.getLong("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLong("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLong("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLong("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLong("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLong("notExisting"); + }); + } + + @Test + public void testGetLongOrDefault() { + assertEquals(10L, this.path.getLongOrDefault("double", 0)); + assertEquals(10L, this.path.getLongOrDefault("float", 0)); + assertEquals(10L, this.path.getLongOrDefault("long", 0)); + assertEquals(10L, this.path.getLongOrDefault("int", 0)); + assertEquals(0L, this.path.getLongOrDefault("null", 0)); + assertEquals(0L, this.path.getLongOrDefault("notExisting", 0)); + + assertThrows(RuntimeException.class, () -> { + this.path.getLongOrDefault("string", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLongOrDefault("boolean", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLongOrDefault("array", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getLongOrDefault("object", 0); + }); + } + + @Test + public void testGetOptionalLong() { + assertEquals(Optional.of(10L), this.path.getOptionalLong("double")); + assertEquals(Optional.of(10L), this.path.getOptionalLong("float")); + assertEquals(Optional.of(10L), this.path.getOptionalLong("long")); + assertEquals(Optional.of(10L), this.path.getOptionalLong("int")); + assertEquals(Optional.empty(), this.path.getOptionalLong("null")); + assertEquals(Optional.empty(), this.path.getOptionalLong("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLong("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLong("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLong("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalLong("object"); + }); + } + + @Test + public void testGetInt() { + assertEquals(10, this.path.getInt("double")); + assertEquals(10, this.path.getInt("float")); + assertEquals(10, this.path.getInt("long")); + assertEquals(10, this.path.getInt("int")); + + assertThrows(RuntimeException.class, () -> { + this.path.getInt("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getInt("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getInt("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getInt("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getInt("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getInt("notExisting"); + }); + } + + @Test + public void testGetIntOrDefault() { + assertEquals(10, this.path.getIntOrDefault("double", 0)); + assertEquals(10, this.path.getIntOrDefault("float", 0)); + assertEquals(10, this.path.getIntOrDefault("long", 0)); + assertEquals(10, this.path.getIntOrDefault("int", 0)); + assertEquals(0, this.path.getIntOrDefault("null", 0)); + assertEquals(0, this.path.getIntOrDefault("notExisting", 0)); + + assertThrows(RuntimeException.class, () -> { + this.path.getIntOrDefault("string", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getIntOrDefault("boolean", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getIntOrDefault("array", 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getIntOrDefault("object", 0); + }); + } + + @Test + public void testGetOptionalInt() { + assertEquals(Optional.of(10), this.path.getOptionalInt("double")); + assertEquals(Optional.of(10), this.path.getOptionalInt("float")); + assertEquals(Optional.of(10), this.path.getOptionalInt("long")); + assertEquals(Optional.of(10), this.path.getOptionalInt("int")); + assertEquals(Optional.empty(), this.path.getOptionalInt("null")); + assertEquals(Optional.empty(), this.path.getOptionalInt("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalInt("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalInt("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalInt("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalInt("object"); + }); + } + + @Test + public void testGetShort() { + assertEquals(10, this.path.getShort("double")); + assertEquals(10, this.path.getShort("float")); + assertEquals(10, this.path.getShort("long")); + assertEquals(10, this.path.getShort("int")); + + assertThrows(RuntimeException.class, () -> { + this.path.getShort("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShort("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShort("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShort("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShort("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShort("notExisting"); + }); + } + + @Test + public void testGetShortOrDefault() { + assertEquals(10, this.path.getShortOrDefault("double", (short) 0)); + assertEquals(10, this.path.getShortOrDefault("float", (short) 0)); + assertEquals(10, this.path.getShortOrDefault("long", (short) 0)); + assertEquals(10, this.path.getShortOrDefault("int", (short) 0)); + assertEquals(0, this.path.getShortOrDefault("null", (short) 0)); + assertEquals(0, this.path.getShortOrDefault("notExisting", (short) 0)); + + assertThrows(RuntimeException.class, () -> { + this.path.getShortOrDefault("string", (short) 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShortOrDefault("boolean", (short) 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShortOrDefault("array", (short) 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getShortOrDefault("object", (short) 0); + }); + } + + @Test + public void testGetOptionalShort() { + assertEquals(Optional.of((short) 10), this.path.getOptionalShort("double")); + assertEquals(Optional.of((short) 10), this.path.getOptionalShort("float")); + assertEquals(Optional.of((short) 10), this.path.getOptionalShort("long")); + assertEquals(Optional.of((short) 10), this.path.getOptionalShort("int")); + assertEquals(Optional.empty(), this.path.getOptionalShort("null")); + assertEquals(Optional.empty(), this.path.getOptionalShort("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalShort("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalShort("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalShort("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalShort("object"); + }); + } + + @Test + public void testGetByte() { + assertEquals(10, this.path.getByte("double")); + assertEquals(10, this.path.getByte("float")); + assertEquals(10, this.path.getByte("long")); + assertEquals(10, this.path.getByte("int")); + + assertThrows(RuntimeException.class, () -> { + this.path.getByte("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByte("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByte("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByte("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByte("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByte("notExisting"); + }); + } + + @Test + public void testGetByteOrDefault() { + assertEquals(10, this.path.getByteOrDefault("double", (byte) 0)); + assertEquals(10, this.path.getByteOrDefault("float", (byte) 0)); + assertEquals(10, this.path.getByteOrDefault("long", (byte) 0)); + assertEquals(10, this.path.getByteOrDefault("int", (byte) 0)); + assertEquals(0, this.path.getByteOrDefault("null", (byte) 0)); + assertEquals(0, this.path.getByteOrDefault("notExisting", (byte) 0)); + + assertThrows(RuntimeException.class, () -> { + this.path.getByteOrDefault("string", (byte) 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByteOrDefault("boolean", (byte) 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByteOrDefault("array", (byte) 0); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getByteOrDefault("object", (byte) 0); + }); + } + + @Test + public void testGetOptionalByte() { + assertEquals(Optional.of((byte) 10), this.path.getOptionalByte("double")); + assertEquals(Optional.of((byte) 10), this.path.getOptionalByte("float")); + assertEquals(Optional.of((byte) 10), this.path.getOptionalByte("long")); + assertEquals(Optional.of((byte) 10), this.path.getOptionalByte("int")); + assertEquals(Optional.empty(), this.path.getOptionalByte("null")); + assertEquals(Optional.empty(), this.path.getOptionalByte("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalByte("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalByte("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalByte("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalByte("object"); + }); + } + + @Test + public void testGetBooleanPath() { + assertNotNull(this.path.getBooleanPath("boolean")); + + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPath("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPath("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPath("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPath("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPath("notExisting"); + }); + } + + @Test + public void testGetBoolean() { + assertTrue(this.path.getBoolean("boolean")); + + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("notExisting"); + }); + } + + @Test + public void testGetBooleanPathNullable() { + assertNotNull(this.path.getBooleanPathNullable("boolean")); + assertNotNull(this.path.getBooleanPathNullable("null")); + assertNotNull(this.path.getBooleanPathNullable("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPathNullable("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPathNullable("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPathNullable("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBooleanPathNullable("object"); + }); + } + + @Test + public void testGetBooleanNullable() { + assertTrue(this.path.getBooleanNullable("boolean")); + assertNull(this.path.getBooleanNullable("null")); + assertNull(this.path.getBooleanNullable("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getBoolean("object"); + }); + } + + @Test + public void testGetOptionalBoolean() { + assertEquals(Optional.of(true), this.path.getOptionalBoolean("boolean")); + assertEquals(Optional.empty(), this.path.getOptionalBoolean("null")); + assertEquals(Optional.empty(), this.path.getOptionalBoolean("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalBoolean("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalBoolean("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalBoolean("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalBoolean("object"); + }); + } + + @Test + public void testGetJsonObjectPath() { + assertNotNull(this.path.getJsonObjectPath("object")); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObjectPath("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObjectPath("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObjectPath("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObjectPath("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObjectPath("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObjectPath("notExisting"); + }); + } + + @Test + public void testGetJsonObject() { + assertNotNull(this.path.getJsonObject("object")); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObject("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObject("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObject("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObject("array"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObject("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonObject("notExisting"); + }); + } + + @Test + public void testGetJsonArrayPath() { + assertNotNull(this.path.getJsonArrayPath("array")); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArrayPath("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArrayPath("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArrayPath("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArrayPath("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArrayPath("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArrayPath("notExisting"); + }); + } + + @Test + public void testGetJsonArray() { + assertNotNull(this.path.getJsonArray("array")); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("object"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("null"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("notExisting"); + }); + } + + @Test + public void testGetJsonArrayOrNull() { + assertNotNull(this.path.getJsonArrayOrNull("array")); + assertNull(this.path.getJsonArrayOrNull("null")); + assertNull(this.path.getJsonArrayOrNull("notExisting")); + + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("string"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("number"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("boolean"); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getJsonArray("object"); + }); + } + + @Test + public void testGetListStringFunctionOfJsonElementPathT() { + final var list = this.path.getList("array", JsonElementPath::getAsString); + assertEquals(1, list.size()); + assertEquals("string", list.get(0)); + + assertThrows(RuntimeException.class, () -> { + this.path.getList("string", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("number", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("boolean", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("object", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("null", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("notExisting", JsonElementPath::getAsString); + }); + } + + @Test + public void testGetListStringJsonSerializerOfT() { + final var list = this.path.getList("array", stringSerializer()); + assertEquals(1, list.size()); + assertEquals("string", list.get(0)); + + assertThrows(RuntimeException.class, () -> { + this.path.getList("string", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("number", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("boolean", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("object", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("null", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("notExisting", stringSerializer()); + }); + } + + @Test + public void testGetOptionalListStringFunctionOfJsonElementPathT() { + final var list = this.path.getOptionalList("array", JsonElementPath::getAsString).orElse(null); + assertEquals(1, list.size()); + assertEquals("string", list.get(0)); + + assertEquals(Optional.empty(), this.path.getOptionalList("null", JsonElementPath::getAsString)); + assertEquals(Optional.empty(), this.path.getOptionalList("notExisting", JsonElementPath::getAsString)); + + assertThrows(RuntimeException.class, () -> { + this.path.getList("string", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("number", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("boolean", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("object", JsonElementPath::getAsString); + }); + } + + @Test + public void testGetOptionalListStringJsonSerializerOfT() { + final var list = this.path.getOptionalList("array", stringSerializer()).orElse(null); + assertEquals(1, list.size()); + assertEquals("string", list.get(0)); + + assertEquals(Optional.empty(), this.path.getOptionalList("null", stringSerializer())); + assertEquals(Optional.empty(), this.path.getOptionalList("notExisting", stringSerializer())); + + assertThrows(RuntimeException.class, () -> { + this.path.getList("string", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("number", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("boolean", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getList("object", stringSerializer()); + }); + } + + @Test + public void testGetArrayStringIntFunctionOfTFunctionOfJsonElementPathT() { + final var array = this.path.getArray("array", String[]::new, JsonElementPath::getAsString); + assertEquals(1, array.length); + assertEquals("string", array[0]); + + assertThrows(RuntimeException.class, () -> { + this.path.getArray("string", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("number", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("boolean", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("object", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("null", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("notExisting", String[]::new, JsonElementPath::getAsString); + }); + } + + @Test + public void testGetArrayStringIntFunctionOfTJsonSerializerOfT() { + final var array = this.path.getArray("array", String[]::new, stringSerializer()); + assertEquals(1, array.length); + assertEquals("string", array[0]); + + assertThrows(RuntimeException.class, () -> { + this.path.getArray("string", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("number", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("boolean", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("object", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("null", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getArray("notExisting", String[]::new, stringSerializer()); + }); + } + + @Test + public void testGetOptionalArrayStringFunctionOfJsonElementPathT() { + final var list = this.path.getOptionalArray("array", String[]::new, JsonElementPath::getAsString).orElse(null); + assertEquals(1, list.length); + assertEquals("string", list[0]); + + assertEquals(Optional.empty(), this.path.getOptionalArray("null", String[]::new, JsonElementPath::getAsString)); + assertEquals(Optional.empty(), + this.path.getOptionalArray("notExisting", String[]::new, JsonElementPath::getAsString)); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("string", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("number", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("boolean", String[]::new, JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("object", String[]::new, JsonElementPath::getAsString); + }); + } + + @Test + public void testGetOptionalArrayStringJsonSerializerOfT() { + final var list = this.path.getOptionalArray("array", String[]::new, stringSerializer()).orElse(null); + assertEquals(1, list.length); + assertEquals("string", list[0]); + + assertEquals(Optional.empty(), this.path.getOptionalArray("null", String[]::new, stringSerializer())); + assertEquals(Optional.empty(), this.path.getOptionalArray("notExisting", String[]::new, stringSerializer())); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("string", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("number", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("boolean", String[]::new, stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalArray("object", String[]::new, stringSerializer()); + }); + } + + @Test + public void testGetSetStringFunctionOfJsonElementPathT() { + final var set = this.path.getSet("array", JsonElementPath::getAsString); + assertEquals(1, set.size()); + assertTrue(set.contains("string")); + + assertThrows(RuntimeException.class, () -> { + this.path.getSet("string", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("number", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("boolean", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("object", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("null", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("notExisting", JsonElementPath::getAsString); + }); + } + + @Test + public void testGetSetStringJsonSerializerOfT() { + final var set = this.path.getSet("array", stringSerializer()); + assertEquals(1, set.size()); + assertTrue(set.contains("string")); + + assertThrows(RuntimeException.class, () -> { + this.path.getSet("string", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("number", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("boolean", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("object", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("null", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getSet("notExisting", stringSerializer()); + }); + } + + @Test + public void testGetOptionalSetStringFunctionOfJsonElementPathT() { + final var set = this.path.getOptionalSet("array", JsonElementPath::getAsString).orElse(null); + assertEquals(1, set.size()); + assertTrue(set.contains("string")); + + assertEquals(Optional.empty(), this.path.getOptionalSet("null", JsonElementPath::getAsString)); + assertEquals(Optional.empty(), this.path.getOptionalSet("notExisting", JsonElementPath::getAsString)); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("string", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("number", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("boolean", JsonElementPath::getAsString); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("object", JsonElementPath::getAsString); + }); + } + + @Test + public void testGetOptionalSetStringJsonSerializerOfT() { + final var set = this.path.getOptionalSet("array", stringSerializer()).orElse(null); + assertEquals(1, set.size()); + assertTrue(set.contains("string")); + + assertEquals(Optional.empty(), this.path.getOptionalSet("null", stringSerializer())); + assertEquals(Optional.empty(), this.path.getOptionalSet("notExisting", stringSerializer())); + + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("string", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("number", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("boolean", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getOptionalSet("object", stringSerializer()); + }); + } + + @Test + public void testGetObject() { + assertEquals("string", this.path.getObject("string", stringSerializer())); + + assertThrows(RuntimeException.class, () -> { + this.path.getObject("number", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObject("boolean", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObject("array", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObject("object", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObject("null", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObject("notExisting", stringSerializer()); + }); + } + + @Test + public void testGetObjectOrNull() { + assertEquals("string", this.path.getObjectOrNull("string", stringSerializer())); + assertNull(this.path.getObjectOrNull("null", stringSerializer())); + assertNull(this.path.getObjectOrNull("notExisting", stringSerializer())); + + assertThrows(RuntimeException.class, () -> { + this.path.getObjectOrNull("number", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObjectOrNull("boolean", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObjectOrNull("array", stringSerializer()); + }); + assertThrows(RuntimeException.class, () -> { + this.path.getObjectOrNull("object", stringSerializer()); + }); + } + + @Test + public void testGet() { + assertNotNull(this.path.get()); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonSerializerTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonSerializerTest.java new file mode 100644 index 00000000000..7b16bdbf32f --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/JsonSerializerTest.java @@ -0,0 +1,76 @@ +package io.openems.common.jsonrpc.serialization; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +import com.google.gson.JsonArray; + +import io.openems.common.utils.JsonUtils; + +public class JsonSerializerTest { + + record SampleRecord(String sampleString) { + + /** + * Returns a {@link JsonSerializer} for a {@link JsonSerializerTest}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SampleRecord.class, json -> { + return new SampleRecord(// + json.getString("sampleString") // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("sampleString", obj.sampleString()) // + .build(); + }); + } + } + + public final JsonSerializer serializer = SampleRecord.serializer(); + + @Test + public void testSimpleObjectSerialize() { + final var expectedString = "expectedString"; + final var serializedObj = this.serializer.serialize(new SampleRecord(expectedString)); + assertEquals(JsonUtils.buildJsonObject() // + .addProperty("sampleString", expectedString) // + .build(), serializedObj); + } + + @Test + public void testSimpleObjectDeserialize() { + final var expectedString = "expectedString"; + final var parsedObj = this.serializer.deserialize(JsonUtils.buildJsonObject() // + .addProperty("sampleString", expectedString) // + .build()); + assertEquals(expectedString, parsedObj.sampleString()); + } + + @Test(expected = RuntimeException.class) + public void testObjectDeserializeOfDifferentType() { + this.serializer.deserialize(new JsonArray()); + } + + @Test + public void testDescriptor() { + final var objectDescriptor = this.serializer.descriptor(); + final var jsonDescription = objectDescriptor.toJson(); + + assertEquals(JsonUtils.buildJsonObject() // + .addProperty("type", "object") // + .addProperty("optional", false) // + .add("properties", JsonUtils.buildJsonObject() // + .add("sampleString", JsonUtils.buildJsonObject() // + .addProperty("type", "string") // + .addProperty("optional", false) // + .build()) + .build()) // + .build(), jsonDescription); + } + +} diff --git a/io.openems.common/test/io/openems/common/jsonrpc/serialization/StringPathActualTest.java b/io.openems.common/test/io/openems/common/jsonrpc/serialization/StringPathActualTest.java new file mode 100644 index 00000000000..24635685215 --- /dev/null +++ b/io.openems.common/test/io/openems/common/jsonrpc/serialization/StringPathActualTest.java @@ -0,0 +1,81 @@ +package io.openems.common.jsonrpc.serialization; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertThrows; +import static org.junit.Assert.assertTrue; + +import java.util.function.Function; + +import org.junit.Test; + +import com.google.gson.JsonObject; + +import io.openems.common.utils.JsonUtils; + +public class StringPathActualTest { + + @Test + public void testFromJsonObjectPathActual() { + final JsonObjectPath json = new JsonObjectPathActual.JsonObjectPathActualNonNull(JsonUtils.buildJsonObject() // + .addProperty("value", "someString") // + .addProperty("nonStringValuePrimitve", 99) // + .add("nonStringValueObject", new JsonObject()) // + .build()); + + assertNotNull(json.getStringPath("value")); + assertThrows(RuntimeException.class, () -> { + json.getStringPath("nonStringValueObject"); + }); + + assertEquals("someString", json.getString("value")); + assertEquals("someString", json.getStringOrNull("value")); + assertNull(json.getStringOrNull("null")); + assertThrows(RuntimeException.class, () -> { + json.getString("nonStringValueObject"); + }); + } + + @Test + public void testStringPathActualNonNull() { + assertThrows(RuntimeException.class, () -> { + new StringPathActual.StringPathActualNonNull(null, Function.identity()); + }); + assertThrows(RuntimeException.class, () -> { + new StringPathActual.StringPathActualNonNull("", null); + }); + + final var stringPath = new StringPathActual.StringPathActualNonNull("string", Function.identity()); + + assertEquals("string", stringPath.get()); + assertEquals("string", stringPath.getRaw()); + } + + @Test + public void testStringPathActualNullableNonNull() { + assertThrows(RuntimeException.class, () -> { + new StringPathActual.StringPathActualNullable("", null); + }); + + final var stringPath = new StringPathActual.StringPathActualNullable("string", Function.identity()); + + assertEquals("string", stringPath.getOrNull()); + assertEquals("string", stringPath.getRawOrNull()); + assertEquals("string", stringPath.getOptional().get()); + } + + @Test + public void testStringPathActualNullableNull() { + assertThrows(RuntimeException.class, () -> { + new StringPathActual.StringPathActualNullable(null, null); + }); + + final var stringPath = new StringPathActual.StringPathActualNullable(null, Function.identity()); + + assertNull(stringPath.getOrNull()); + assertNull(stringPath.getRawOrNull()); + assertTrue(stringPath.getOptional().isEmpty()); + } + +} diff --git a/io.openems.common/test/io/openems/common/utils/FunctionUtilsTest.java b/io.openems.common/test/io/openems/common/utils/FunctionUtilsTest.java index 839149a9357..0ad3943f540 100644 --- a/io.openems.common/test/io/openems/common/utils/FunctionUtilsTest.java +++ b/io.openems.common/test/io/openems/common/utils/FunctionUtilsTest.java @@ -3,6 +3,7 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiConsumer; import java.util.function.Consumer; @@ -72,4 +73,15 @@ public void testSupplier() { assertEquals("success", supplier.get()); } + @Test + public void testLazySingleton() throws Exception { + final var count = new AtomicInteger(0); + final var singleton = FunctionUtils.lazySingleton(count::incrementAndGet); + + assertEquals(1, singleton.get().intValue()); + assertEquals(1, singleton.get().intValue()); + assertEquals(2, count.incrementAndGet()); + assertEquals(1, singleton.get().intValue()); + } + } diff --git a/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java index 9ea55060009..48c10b5f99e 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java +++ b/io.openems.edge.common/src/io/openems/edge/common/component/ComponentManager.java @@ -11,6 +11,9 @@ import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.types.ChannelAddress; import io.openems.common.types.EdgeConfig; import io.openems.edge.common.channel.Channel; @@ -299,7 +302,7 @@ public default > T getChannel(ChannelAddress channelAddress * @param request the {@link CreateComponentConfigRequest} * @throws OpenemsNamedException on error */ - public void handleCreateComponentConfigRequest(User user, CreateComponentConfigRequest request) + public void handleCreateComponentConfigRequest(User user, CreateComponentConfig.Request request) throws OpenemsNamedException; /** @@ -309,7 +312,7 @@ public void handleCreateComponentConfigRequest(User user, CreateComponentConfigR * @param request the {@link UpdateComponentConfigRequest} * @throws OpenemsNamedException on error */ - public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigRequest request) + public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfig.Request request) throws OpenemsNamedException; /** @@ -319,7 +322,7 @@ public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigR * @param request the {@link DeleteComponentConfigRequest} * @throws OpenemsNamedException on error */ - public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfigRequest request) + public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfig.Request request) throws OpenemsNamedException; } \ No newline at end of file diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java index e12c09553fa..84c47b6ff10 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/ComponentJsonApi.java @@ -1,5 +1,11 @@ package io.openems.edge.common.jsonapi; +import io.openems.common.jsonrpc.request.ComponentJsonApiRequest; + +/** + * Declares a class as being able to handle JSON-RPC Requests which were send in + * a {@link ComponentJsonApiRequest}. + */ public interface ComponentJsonApi extends JsonApi { /** diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilder.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilder.java index cde58c31b0b..aff00596b4b 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilder.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilder.java @@ -4,14 +4,21 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.gson.JsonArray; +import io.openems.common.jsonrpc.serialization.JsonElementPathDummy; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; public final class EndpointRequestDefinitionBuilder { + private static final Logger LOG = LoggerFactory.getLogger(EndpointRequestDefinitionBuilder.class); + private JsonSerializer serializer; private final List> examples = new ArrayList<>(); @@ -25,6 +32,12 @@ public EndpointRequestDefinitionBuilder setSerializer(// JsonSerializer serializer // ) { this.serializer = serializer; + try { + this.addExample("", + serializer.deserializePath(new JsonElementPathDummy.JsonElementPathDummyNonNull())); + } catch (RuntimeException e) { + LOG.info("Unable to automatically generated request with " + serializer); + } return this; } @@ -68,10 +81,18 @@ public List> getExamples() { */ public JsonArray createExampleArray() { return this.examples.stream() // - .map(t -> JsonUtils.buildJsonObject() // - .addProperty("key", t.identifier()) // - .add("value", this.serializer.serialize(t.exampleObject())) // - .build()) // + .map(t -> { + try { + return JsonUtils.buildJsonObject() // + .addProperty("key", t.identifier()) // + .add("value", this.serializer.serialize(t.exampleObject())) // + .build(); + } catch (RuntimeException e) { + LOG.error("Unable to create json example for " + t, e); + return null; + } + }) // + .filter(Objects::nonNull) // .collect(toJsonArray()); } diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilder.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilder.java index fc5a03a3389..e9e2f4aae9d 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilder.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilder.java @@ -4,14 +4,21 @@ import java.util.ArrayList; import java.util.List; +import java.util.Objects; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import com.google.gson.JsonArray; +import io.openems.common.jsonrpc.serialization.JsonElementPathDummy; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; public final class EndpointResponseDefinitionBuilder { + private static final Logger LOG = LoggerFactory.getLogger(EndpointResponseDefinitionBuilder.class); + private JsonSerializer serializer; private final List> examples = new ArrayList<>(); @@ -25,6 +32,12 @@ public EndpointResponseDefinitionBuilder setSerializer(// JsonSerializer serializer // ) { this.serializer = serializer; + try { + this.addExample("", + serializer.deserializePath(new JsonElementPathDummy.JsonElementPathDummyNonNull())); + } catch (RuntimeException e) { + LOG.info("Unable to automatically generated response with " + serializer); + } return this; } @@ -73,10 +86,18 @@ public List> getExamples() { */ public JsonArray createExampleArray() { return this.examples.stream() // - .map(t -> JsonUtils.buildJsonObject() // - .addProperty("key", t.identifier()) // - .add("value", this.serializer.serialize(t.exampleObject())) // - .build()) // + .map(t -> { + try { + return JsonUtils.buildJsonObject() // + .addProperty("key", t.identifier()) // + .add("value", this.serializer.serialize(t.exampleObject())) // + .build(); + } catch (RuntimeException e) { + LOG.error("Unable to create json example for " + t, e); + return null; + } + }) // + .filter(Objects::nonNull) // .collect(toJsonArray()); } diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApi.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApi.java index 1b78a9b7ba5..9c7a9122433 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApi.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApi.java @@ -1,7 +1,20 @@ package io.openems.edge.common.jsonapi; +import io.openems.common.jsonrpc.request.ComponentJsonApiRequest; + /** * Declares a class as being able to handle JSON-RPC Requests. + * + *

+ * NOTE: the routes are not automatically available somewhere they must have + * some kind of entry point see {@link io.openems.edge.controller.api.backend}, + * {@link io.openems.edge.controller.api.rest}, + * {@link io.openems.edge.controller.api.websocket}. + * + *

+ * Most of the times on edge site only {@link ComponentJsonApiRequest} are + * required, for them use the interface {@link ComponentJsonApi}, because all + * instances of it are automatically binded and available with this request. */ public interface JsonApi { diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApiBuilder.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApiBuilder.java index fef18e45bf0..baa41c5fcb5 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApiBuilder.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonApiBuilder.java @@ -24,6 +24,7 @@ import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponse; import io.openems.common.jsonrpc.base.JsonrpcResponseError; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.FunctionUtils; @@ -105,6 +106,23 @@ public JsonApiBuilder handleRequest(// }, endpointType.getRequestSerializer(), endpointType.getResponseSerializer()); } + /** + * Adds a rpc request handler to the current builder. + * + * @param the type of the request + * @param the type of the response + * @param endpointType the {@link EndpointRequestType} of the handled rpc + * request + * @param handler the handler of the request + * @return this + */ + public JsonApiBuilder handleRequest(// + final EndpointRequestType endpointType, // + final ThrowingFunction, RESPONSE, Exception> handler // + ) { + return this.handleRequest(endpointType, FunctionUtils::doNothing, handler); + } + /** * Adds a rpc handler to the current builder. * @@ -278,6 +296,8 @@ public JsonApiBuilder rpc(// /** * Delegates the handled request to another endpoint. * + * @param the type of the request + * @param the type of the response * @param method the method of the handled rpc request * @param defBuilder the builder for the {@link EndpointDefinitionBuilder} * @param handler the handler of the request which returns the delegated @@ -288,15 +308,15 @@ public JsonApiBuilder rpc(// * @param subroutes the subroutes which can be reached with this handler * @return this */ - public JsonApiBuilder delegate(// + public JsonApiBuilder delegate(// final String method, // - final Consumer> defBuilder, // + final Consumer> defBuilder, // final ThrowingFunction, JsonrpcRequest, Exception> handler, // final Function builder, // final Function responseMapper, // final Supplier> subroutes // ) { - final var endpointDef = new EndpointDefinitionBuilder(); + final var endpointDef = new EndpointDefinitionBuilder(); defBuilder.accept(endpointDef); this.addEndpoint(new JsonApiEndpoint(method, (b, t) -> { try { @@ -397,6 +417,60 @@ public JsonApiBuilder delegate(// return this.delegate(method, FunctionUtils::doNothing, handler, Function.identity()); } + /** + * Delegates the handled request to another endpoint. + * + * @param the type of the request + * @param the type of the response + * @param requestType the {@link EndpointRequestType} of the handled rpc + * request + * @param defBuilder the builder for the {@link EndpointDefinitionBuilder} + * @param handler the handler of the request which returns the delegated + * request + * @param builder the path to the builder which handles the delegated + * request + * @param responseMapper the mapper of the response + * @param subroutes the subroutes which can be reached with this handler + * @return this + */ + public JsonApiBuilder delegate(// + final EndpointRequestType requestType, // + final Consumer> defBuilder, // + final ThrowingFunction, JsonrpcRequest, Exception> handler, // + final Function builder, // + final Function responseMapper, // + final Supplier> subroutes // + ) { + return this.delegate(requestType.getMethod(), t -> { + t.setRequestSerializer(requestType.getRequestSerializer()); + t.setResponseSerializer(requestType.getResponseSerializer()); + defBuilder.accept(t); + }, handler, builder, responseMapper, subroutes); + } + + /** + * Delegates the handled request to another endpoint. + * + * @param the type of the request + * @param the type of the response + * @param requestType the {@link EndpointRequestType} of the handled rpc request + * @param defBuilder the builder for the {@link EndpointDefinitionBuilder} + * @param handler the handler of the request which returns the delegated + * request + * @return this + */ + public JsonApiBuilder delegate(// + final EndpointRequestType requestType, // + final Consumer> defBuilder, // + final ThrowingFunction, JsonrpcRequest, Exception> handler // + ) { + return this.delegate(requestType, FunctionUtils::doNothing, t -> { + return handler + .apply(t.mapRequest(requestType.getRequestSerializer().deserialize(t.getRequest().getParams())) + .mapResponse()); + }, Function.identity(), Function.identity(), null); + } + private void addEndpoint(JsonApiEndpoint endpoint) { synchronized (this.endpoints) { final var previous = this.endpoints.put(endpoint.getMethod(), endpoint); @@ -526,7 +600,7 @@ public void close() { } private JsonrpcResponseError handleException(Call call, Throwable t) { - if (this.isDebug()) { + if (this.isDebug() || true) { this.log.error(t.getMessage(), t); } diff --git a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonrpcRoleEndpointGuard.java b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonrpcRoleEndpointGuard.java index a21d6df0994..e0d7b08caa1 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonrpcRoleEndpointGuard.java +++ b/io.openems.edge.common/src/io/openems/edge/common/jsonapi/JsonrpcRoleEndpointGuard.java @@ -32,8 +32,7 @@ public Role getRole() { */ public static JsonSerializer serializer() { return jsonObjectSerializer(JsonrpcRoleEndpointGuard.class, - t -> new JsonrpcRoleEndpointGuard(Role.getRole(t.getStringPath("role") // - .get())), + t -> new JsonrpcRoleEndpointGuard(t.getEnum("role", Role.class)), // t -> JsonUtils.buildJsonObject() // .addProperty("name", "role") // .addProperty("description", "User-Role has to be at least " + t.getRole()) // diff --git a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java index 63c3ba3d539..2117b2cf109 100644 --- a/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java +++ b/io.openems.edge.common/src/io/openems/edge/common/test/DummyComponentManager.java @@ -24,12 +24,12 @@ import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; import io.openems.common.jsonrpc.request.GetEdgeConfigRequest; -import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.response.GetEdgeConfigResponse; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.common.utils.StreamUtils; @@ -178,32 +178,30 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) { return new GetEdgeConfigResponse(call.getRequest().getId(), this.getEdgeConfig()); }); - builder.handleRequest(CreateComponentConfigRequest.METHOD, call -> { - final var request = CreateComponentConfigRequest.from(call.getRequest()); - this.handleCreateComponentConfigRequest(call.get(EdgeKeys.USER_KEY), request); + builder.handleRequest(new CreateComponentConfig(), endpoint -> { - return new GenericJsonrpcResponseSuccess(request.getId()); + }, call -> { + this.handleCreateComponentConfigRequest(call.get(EdgeKeys.USER_KEY), call.getRequest()); + return EmptyObject.INSTANCE; }); - builder.handleRequest(UpdateComponentConfigRequest.METHOD, call -> { - final var request = UpdateComponentConfigRequest.from(call.getRequest()); - this.handleUpdateComponentConfigRequest(call.get(EdgeKeys.USER_KEY), request); - - return new GenericJsonrpcResponseSuccess(request.getId()); + builder.handleRequest(new UpdateComponentConfig(), call -> { + this.handleUpdateComponentConfigRequest(call.get(EdgeKeys.USER_KEY), call.getRequest()); + return EmptyObject.INSTANCE; }); } @Override - public void handleCreateComponentConfigRequest(User user, CreateComponentConfigRequest request) + public void handleCreateComponentConfigRequest(User user, CreateComponentConfig.Request request) throws OpenemsNamedException { if (this.configurationAdmin == null) { throw new OpenemsException("Can not create Component Config. ConfigurationAdmin is null!"); } try { - var config = this.configurationAdmin.createFactoryConfiguration(request.getFactoryPid(), null); + var config = this.configurationAdmin.createFactoryConfiguration(request.factoryPid(), null); // set properties - for (var property : request.getProperties()) { + for (var property : request.properties()) { var value = JsonUtils.getAsBestType(property.getValue()); if (value instanceof Object[] os && os.length == 0) { value = new String[0]; @@ -218,7 +216,7 @@ public void handleCreateComponentConfigRequest(User user, CreateComponentConfigR } @Override - public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigRequest request) + public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfig.Request request) throws OpenemsNamedException { if (this.configurationAdmin == null) { throw new OpenemsException("Can not update Component Config. ConfigurationAdmin is null!"); @@ -229,11 +227,11 @@ public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigR if (props == null) { continue; } - if (props.get("id") == null || !props.get("id").equals(request.getComponentId())) { + if (props.get("id") == null || !props.get("id").equals(request.componentId())) { continue; } var properties = new Hashtable(); - for (var property : request.getProperties()) { + for (var property : request.properties()) { properties.put(property.getName(), property.getValue()); } configuration.update(properties); @@ -244,7 +242,7 @@ public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigR } @Override - public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfigRequest request) + public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfig.Request request) throws OpenemsNamedException { if (this.configurationAdmin == null) { throw new OpenemsException("Can not delete Component Config. ConfigurationAdmin is null!"); @@ -256,7 +254,7 @@ public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfigR if (props == null) { continue; } - if (props.get("id") == null || !props.get("id").equals(request.getComponentId())) { + if (props.get("id") == null || !props.get("id").equals(request.componentId())) { continue; } configuration.delete(); @@ -286,4 +284,4 @@ public Map getComponentProperties(String componentId) { } } -} \ No newline at end of file +} diff --git a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EmptyObject.java b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EmptyObject.java deleted file mode 100644 index 6c8d0cdc20f..00000000000 --- a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EmptyObject.java +++ /dev/null @@ -1,19 +0,0 @@ -package io.openems.edge.common.jsonapi; - -import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.emptyObjectSerializer; - -import io.openems.common.jsonrpc.serialization.JsonSerializer; - -public record EmptyObject() { - - /** - * Returns a {@link JsonSerializer} for a - * {@link JsonApiBuilderTest.EmptyObject}. - * - * @return the created {@link JsonSerializer} - */ - public static JsonSerializer serializer() { - return emptyObjectSerializer(EmptyObject::new); - } - -} \ No newline at end of file diff --git a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilderTest.java b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilderTest.java index 2c6e46460c9..98b3d340959 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilderTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointRequestDefinitionBuilderTest.java @@ -5,6 +5,8 @@ import org.junit.Test; +import io.openems.common.jsonrpc.serialization.EmptyObject; + public class EndpointRequestDefinitionBuilderTest { @Test @@ -19,7 +21,7 @@ public void testAddExampleStringRequest() { final var defBuilder = new EndpointRequestDefinitionBuilder(); final var exampleKey = "exampleKey"; - final var exampleObject = new EmptyObject(); + final var exampleObject = EmptyObject.INSTANCE; defBuilder.addExample(exampleKey, exampleObject); assertEquals(1, defBuilder.getExamples().size()); assertEquals(exampleKey, defBuilder.getExamples().get(0).identifier()); @@ -30,7 +32,7 @@ public void testAddExampleStringRequest() { public void testAddExampleRequest() { final var defBuilder = new EndpointRequestDefinitionBuilder(); - final var exampleObject = new EmptyObject(); + final var exampleObject = EmptyObject.INSTANCE; defBuilder.addExample(exampleObject); assertEquals(1, defBuilder.getExamples().size()); assertEquals(exampleObject, defBuilder.getExamples().get(0).exampleObject()); @@ -39,10 +41,10 @@ public void testAddExampleRequest() { @Test public void testCreateExampleArray() { final var defBuilder = new EndpointRequestDefinitionBuilder(); - defBuilder.addExample(new EmptyObject()); + defBuilder.addExample(EmptyObject.INSTANCE); defBuilder.setSerializer(EmptyObject.serializer()); - assertEquals(1, defBuilder.createExampleArray().size()); + assertEquals(2, defBuilder.createExampleArray().size()); } } diff --git a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilderTest.java b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilderTest.java index 487d69b6937..ffee66c5160 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilderTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/EndpointResponseDefinitionBuilderTest.java @@ -5,6 +5,8 @@ import org.junit.Test; +import io.openems.common.jsonrpc.serialization.EmptyObject; + public class EndpointResponseDefinitionBuilderTest { @Test @@ -19,7 +21,7 @@ public void testAddExampleStringResponse() { final var defBuilder = new EndpointResponseDefinitionBuilder(); final var exampleKey = "exampleKey"; - final var exampleObject = new EmptyObject(); + final var exampleObject = EmptyObject.INSTANCE; defBuilder.addExample(exampleKey, exampleObject); assertEquals(1, defBuilder.getExamples().size()); assertEquals(exampleKey, defBuilder.getExamples().get(0).identifier()); @@ -30,7 +32,7 @@ public void testAddExampleStringResponse() { public void testAddExampleResponse() { final var defBuilder = new EndpointResponseDefinitionBuilder(); - final var exampleObject = new EmptyObject(); + final var exampleObject = EmptyObject.INSTANCE; defBuilder.addExample(exampleObject); assertEquals(1, defBuilder.getExamples().size()); assertEquals(exampleObject, defBuilder.getExamples().get(0).exampleObject()); @@ -39,10 +41,10 @@ public void testAddExampleResponse() { @Test public void testCreateExampleArray() { final var defBuilder = new EndpointResponseDefinitionBuilder(); - defBuilder.addExample(new EmptyObject()); + defBuilder.addExample(EmptyObject.INSTANCE); defBuilder.setSerializer(EmptyObject.serializer()); - assertEquals(1, defBuilder.createExampleArray().size()); + assertEquals(2, defBuilder.createExampleArray().size()); } } diff --git a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/JsonApiBuilderTest.java b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/JsonApiBuilderTest.java index 290d565adbf..2408c1b23ef 100644 --- a/io.openems.edge.common/test/io/openems/edge/common/jsonapi/JsonApiBuilderTest.java +++ b/io.openems.edge.common/test/io/openems/edge/common/jsonapi/JsonApiBuilderTest.java @@ -17,6 +17,7 @@ import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; import io.openems.common.jsonrpc.base.JsonrpcRequest; import io.openems.common.jsonrpc.base.JsonrpcResponse; +import io.openems.common.jsonrpc.serialization.EmptyObject; import io.openems.common.utils.FunctionUtils; public class JsonApiBuilderTest { diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java index 4ae340bec7e..24c704fe133 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/CommonProps.java @@ -80,8 +80,6 @@ PARAM extends BundleParameter> AppDef installationHint(// .build()); }); fields.add(JsonFormlyUtil.buildCheckboxFromNameable(property) // - .isRequired(true) // - .requireTrue(l) // .setLabel(TranslationUtil.getTranslation(parameter.bundle, "acceptCondition.label")) // .build()); field.setFieldGroup(fields.build()); diff --git a/io.openems.edge.core/src/io/openems/edge/app/common/props/RelayProps.java b/io.openems.edge.core/src/io/openems/edge/app/common/props/RelayProps.java index f8275bd4fc8..aae6cf80b1c 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/common/props/RelayProps.java +++ b/io.openems.edge.core/src/io/openems/edge/app/common/props/RelayProps.java @@ -2,6 +2,7 @@ import static io.openems.common.utils.JsonUtils.toJsonArray; import static io.openems.edge.app.common.props.CommonProps.defaultDef; +import static io.openems.edge.core.appmanager.TranslationUtil.translate; import static io.openems.edge.core.appmanager.formly.builder.selectgroup.Option.buildOption; import static io.openems.edge.core.appmanager.formly.builder.selectgroup.OptionGroup.buildOptionGroup; import static java.util.Collections.emptyList; @@ -16,6 +17,7 @@ import java.util.function.Function; import java.util.function.Predicate; +import com.google.gson.JsonArray; import com.google.gson.JsonNull; import com.google.gson.JsonPrimitive; @@ -59,6 +61,90 @@ public record RelayContactFilter(// BiFunction> disabledReasons // ) { + /** + * Creates a new {@link RelayContactFilter}. + * + * @return the {@link RelayContactFilter} + */ + public static RelayContactFilter create() { + return new RelayContactFilter(null, null, null, null, null); + } + + public RelayContactFilter { + if (componentFilter == null) { + componentFilter = t -> true; + } + if (channelFilter == null) { + channelFilter = (t, d) -> true; + } + if (disabledReasons == null) { + disabledReasons = (t, d) -> emptyList(); + } + } + + /** + * Creates a copy of the current {@link RelayContactFilter} with the + * componentFilter replaced with the new one. + * + * @param componentFilter the new componentFilter + * @return a new {@link RelayContactFilter} + */ + public RelayContactFilter withComponentFilter(Predicate componentFilter) { + return new RelayContactFilter(componentFilter == null ? t -> true : componentFilter, + this.componentAliasMapper, this.channelFilter, this.channelAliasMapper, this.disabledReasons); + } + + /** + * Creates a copy of the current {@link RelayContactFilter} with the + * componentAliasMapper replaced with the new one. + * + * @param componentAliasMapper the new componentAliasMapper + * @return a new {@link RelayContactFilter} + */ + public RelayContactFilter withComponentAliasMapper(Function componentAliasMapper) { + return new RelayContactFilter(this.componentFilter, componentAliasMapper, this.channelFilter, + this.channelAliasMapper, this.disabledReasons); + } + + /** + * Creates a copy of the current {@link RelayContactFilter} with the + * channelFilter replaced with the new one. + * + * @param channelFilter the new channelFilter + * @return a new {@link RelayContactFilter} + */ + public RelayContactFilter withChannelFilter(BiPredicate channelFilter) { + return new RelayContactFilter(this.componentFilter, this.componentAliasMapper, + channelFilter == null ? (t, u) -> true : channelFilter, this.channelAliasMapper, + this.disabledReasons); + } + + /** + * Creates a copy of the current {@link RelayContactFilter} with the + * channelAliasMapper replaced with the new one. + * + * @param channelAliasMapper the new channelAliasMapper + * @return a new {@link RelayContactFilter} + */ + public RelayContactFilter withChannelAliasMapper( + BiFunction channelAliasMapper) { + return new RelayContactFilter(this.componentFilter, this.componentAliasMapper, this.channelFilter, + channelAliasMapper, this.disabledReasons); + } + + /** + * Creates a copy of the current {@link RelayContactFilter} with the + * disabledReasons replaced with the new one. + * + * @param disabledReasons the new disabledReasons + * @return a new {@link RelayContactFilter} + */ + public RelayContactFilter withDisabledReasons( + BiFunction> disabledReasons) { + return new RelayContactFilter(this.componentFilter, this.componentAliasMapper, this.channelFilter, + this.channelAliasMapper, disabledReasons == null ? (t, u) -> emptyList() : disabledReasons); + } + } /** @@ -130,7 +216,7 @@ public static RelayContactInformation createPhaseInformation(// * @return the {@link RelayContactFilter} */ public static RelayContactFilter emptyFilter() { - return new RelayContactFilter(t -> true, null, (t, u) -> true, null, (t, u) -> emptyList()); + return RelayContactFilter.create(); } /** @@ -151,10 +237,9 @@ public static RelayContactFilter feneconHomeFilter(// return emptyFilter(); } final var bundle = AbstractOpenemsApp.getTranslationBundle(l); - return new RelayContactFilter(// - t -> true, // - null, // - (component, channel) -> { + + return RelayContactFilter.create() // + .withChannelFilter((component, channel) -> { if ("io0".equals(component.id())) { if (List.of("Relay4", "Relay7", "Relay8").stream() // .anyMatch(c -> c.equals(channel.channelId().id()))) { @@ -162,9 +247,9 @@ public static RelayContactFilter feneconHomeFilter(// } } return true; - }, // - (component, channel) -> relayAliasMapper(channel), // - (component, channel) -> { + }) // + .withChannelAliasMapper((component, channel) -> relayAliasMapper(channel)) // + .withDisabledReasons((component, channel) -> { if (!onlyHighVoltageRelays) { return emptyList(); } @@ -175,8 +260,7 @@ public static RelayContactFilter feneconHomeFilter(// } } return emptyList(); - } // - ); + }); } /** @@ -185,7 +269,8 @@ public static RelayContactFilter feneconHomeFilter(// * @return the {@link RelayContactFilter} */ public static RelayContactFilter gpioFilter() { - return new RelayContactFilter(t -> !t.serviceFactoryPid().equals("IO.Gpio"), null, null, null, null); + return RelayContactFilter.create() // + .withComponentFilter(t -> !t.serviceFactoryPid().equals("IO.Gpio")); } /** @@ -278,6 +363,9 @@ AppDef relayContactDef(boolean isMulti, int contactPosi final var preferredRelay = parameter.relayContactInformation().preferredRelays[contactPosition - 1]; final var value = preferredRelay == null ? JsonNull.INSTANCE : new JsonPrimitive(preferredRelay); if (isMulti) { + if (value.isJsonNull()) { + return new JsonArray(); + } return JsonUtils.buildJsonArray() // .add(value) // .build(); @@ -286,28 +374,45 @@ AppDef relayContactDef(boolean isMulti, int contactPosi }); def.setField(JsonFormlyUtil::buildSelectGroupFromNameable, (app, property, l, parameter, field) -> { field.setMulti(isMulti); + field.setMissingOptionsText(translate(parameter.bundle(), "relay.missingOptions")); final var information = parameter.relayContactInformation(); final var defaultString = " (" + TranslationUtil.getTranslation(parameter.bundle(), "relay.defaultRelayContact") + ")"; + final BiFunction singleDisabledExpression = (nameable, + channel) -> { + return Exp.initialModelValue(nameable).isArray() + .ifElse(Exp.initialModelValue(nameable).asArray() + .every(t -> t.notEqual(Exp.staticValue(channel.channel()))), + Exp.initialModelValue(nameable).notEqual(Exp.staticValue(channel.channel()))); + }; + final Function disabledExpressionFunction = channel -> { if (channel.usingComponents().isEmpty() // && channel.disabledReasons().isEmpty()) { return null; } - var exp = Exp.initialModelValue(property).notEqual(Exp.staticValue(channel.channel())); + var exp = singleDisabledExpression.apply(property, channel); for (final var nameable : allContacts) { if (nameable.name().equals(property.name())) { continue; } - exp = exp.and(Exp.initialModelValue(nameable).notEqual(Exp.staticValue(channel.channel()))); + exp = exp.and(singleDisabledExpression.apply(nameable, channel)); } return exp; }; + final BiFunction singleTitleExpression = (nameable, + channel) -> { + return Exp.initialModelValue(nameable).isArray() + .ifElse(Exp.initialModelValue(nameable).asArray() + .some(t -> t.equal(Exp.staticValue(channel.channel()))), + Exp.initialModelValue(nameable).equal(Exp.staticValue(channel.channel()))); + }; + final BiFunction titleExpressionFunction = (relayInfo, channelInfo) -> { final var isDefault = information.defaultRelays().stream() // @@ -329,12 +434,12 @@ AppDef relayContactDef(boolean isMulti, int contactPosi .map(t -> "\\'" + t + "\\'") // .collect(joining(", ")); - var exp = Exp.initialModelValue(property).equal(Exp.staticValue(channelInfo.channel())); + var exp = singleTitleExpression.apply(property, channelInfo); for (final var nameable : allContacts) { if (nameable.name().equals(property.name())) { continue; } - exp = exp.or(Exp.initialModelValue(nameable).equal(Exp.staticValue(channelInfo.channel()))); + exp = exp.or(singleTitleExpression.apply(nameable, channelInfo)); } return Exp.ifElse(exp, // diff --git a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java index 43cbc2dcf1a..821b7424a25 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java +++ b/io.openems.edge.core/src/io/openems/edge/app/hardware/KMtronic8Channel.java @@ -38,6 +38,7 @@ import io.openems.edge.core.appmanager.Type.Parameter; import io.openems.edge.core.appmanager.Type.Parameter.BundleParameter; import io.openems.edge.core.appmanager.dependency.Tasks; +import io.openems.edge.core.appmanager.formly.Exp; /** * Describes a App for KMtronic 8-Channel Relay. @@ -73,9 +74,18 @@ public static enum Property implements Type def.setTranslatedDescriptionWithAppPrefix(".ip.description") // .setDefaultValue("192.168.1.199") // .setRequired(true))), // - CHECK(AppDef.copyOfGeneric(CommonProps.installationHint(// - (app, property, l, parameter) -> TranslationUtil.getTranslation(parameter.bundle, // - "App.Hardware.KMtronic8Channel.installationHint")))), // + CHECK(AppDef.copyOfGeneric(CommonProps.installationHint((app, property, l, parameter) -> { + return TranslationUtil.getTranslation(parameter.bundle, // + "App.Hardware.KMtronic8Channel.installationHint"); + })) // + .setRequired(true) // + .wrapField((app, property, l, parameter, field) -> { + field.requireTrue(l); + + // TODO find better way to distinguish if the current form is for installing or + // updating + field.onlyShowIf(Exp.currentModelValue(IO_ID).isNull()); + })), // ; private final AppDef def; diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java index 3f1ca372b13..b72e225efcf 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/CombinedHeatAndPower.java @@ -208,7 +208,8 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(checkRelayCount(1, CheckRelayCountFilters.feneconHome(false))); + .setInstallableCheckableConfigs(checkRelayCount(1, CheckRelayCountFilters.feneconHome(false), + CheckRelayCountFilters.deviceHardware())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java index 1a7c9bcb76b..310665da5c2 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatPump.java @@ -206,7 +206,8 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(checkRelayCount(2, CheckRelayCountFilters.feneconHome(false))); + .setInstallableCheckableConfigs(checkRelayCount(2, CheckRelayCountFilters.feneconHome(false), + CheckRelayCountFilters.deviceHardware())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java index d483ac7ef6b..7f454f752ce 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java +++ b/io.openems.edge.core/src/io/openems/edge/app/heat/HeatingElement.java @@ -250,7 +250,8 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(checkRelayCount(3, CheckRelayCountFilters.feneconHome(true))); + .setInstallableCheckableConfigs(checkRelayCount(3, CheckRelayCountFilters.feneconHome(true), + CheckRelayCountFilters.deviceHardware())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java index eac82b5b982..e95e1011328 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ManualRelayControl.java @@ -86,7 +86,8 @@ public static enum Property implements Type def// .setTranslatedLabelWithAppPrefix(".outputChannel.label") // - .setTranslatedDescriptionWithAppPrefix(".outputChannel.description"))), // + .setTranslatedDescriptionWithAppPrefix(".outputChannel.description")) // + .setRequired(true)), // ; private final AppDef def; @@ -174,7 +175,8 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(checkRelayCount(1, CheckRelayCountFilters.feneconHome(true))); + .setInstallableCheckableConfigs(checkRelayCount(1, CheckRelayCountFilters.feneconHome(true), + CheckRelayCountFilters.deviceHardware())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java index 23e3d80be1c..f1d32ace24a 100644 --- a/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java +++ b/io.openems.edge.core/src/io/openems/edge/app/loadcontrol/ThresholdControl.java @@ -87,7 +87,8 @@ public static enum Property implements Type def// .setTranslatedLabelWithAppPrefix(".outputChannels.label") // - .setTranslatedDescriptionWithAppPrefix(".outputChannels.description"))), // + .setTranslatedDescriptionWithAppPrefix(".outputChannels.description") // + .setRequired(true))), // ; private final AppDef def; @@ -145,17 +146,17 @@ protected ThrowingTriFunction, L final var ctrlIoChannelSingleThresholdId = this.getId(t, p, Property.CTRL_IO_CHANNEL_SINGLE_THRESHOLD_ID); - final var alias = this.getValueOrDefault(p, Property.ALIAS, this.getName(l)); + final var alias = this.getString(p, l, Property.ALIAS); final var outputChannelAddress = this.getJsonArray(p, Property.OUTPUT_CHANNELS); - var components = Lists.newArrayList(// + final var components = Lists.newArrayList(// new EdgeConfig.Component(ctrlIoChannelSingleThresholdId, alias, "Controller.IO.ChannelSingleThreshold", JsonUtils.buildJsonObject() // .onlyIf(t == ConfigurationTarget.ADD, - j -> j.addProperty("inputChannelAddress", "_sum/EssSoc")) + j -> j.addProperty("inputChannelAddress", "_sum/EssSoc") // + .addProperty("threshold", 50)) .add("outputChannelAddress", outputChannelAddress) // - .onlyIf(t == ConfigurationTarget.ADD, b -> b.addProperty("threshold", 50)) // .build()) // ); @@ -180,7 +181,8 @@ public OpenemsAppCategory[] getCategories() { @Override public ValidatorConfig.Builder getValidateBuilder() { return ValidatorConfig.create() // - .setInstallableCheckableConfigs(checkRelayCount(1, CheckRelayCountFilters.feneconHome(true))); + .setInstallableCheckableConfigs(checkRelayCount(1, CheckRelayCountFilters.feneconHome(true), + CheckRelayCountFilters.deviceHardware())); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java index 8d74cdc6c96..66a25da9ae7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/AppManagerImpl.java @@ -48,6 +48,8 @@ import io.openems.common.function.ThrowingSupplier; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.oem.OpenemsEdgeOem; import io.openems.common.session.Language; import io.openems.common.session.Role; @@ -774,12 +776,11 @@ private GetApp.Response handleGetAppRequest(User user, GetApp.Request request) t /** * Handles a {@link GetApps.Request}. * - * @param user the User - * @param request the {@link GetApps.Request} + * @param user the User * @return the Future JSON-RPC Response * @throws OpenemsNamedException on error */ - private GetApps.Response handleGetAppsRequest(User user, GetApps.Request request) throws OpenemsNamedException { + private GetApps.Response handleGetAppsRequest(User user) throws OpenemsNamedException { return GetApps.Response.newInstance(this.availableApps, this.instantiatedApps, user.getRole(), user.getLanguage(), this.validator); } @@ -791,11 +792,7 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) { Gets all available apps on the current edge. """.stripIndent()); - endpoint.applyRequestBuilder(request -> { - request.addExample(new GetApps.Request()); - }); - - }, call -> this.handleGetAppsRequest(call.get(EdgeKeys.USER_KEY), call.getRequest())); + }, call -> this.handleGetAppsRequest(call.get(EdgeKeys.USER_KEY))); builder.handleRequest(new GetApp(), endpoint -> { endpoint.setDescription(""" @@ -912,7 +909,10 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) { endpoint.setGuards(EdgeGuards.roleIsAtleast(Role.OWNER)); - }, call -> this.handleUpdateAppConfigRequest(call.get(EdgeKeys.USER_KEY), call.getRequest())); + }, call -> { + this.handleUpdateAppConfigRequest(call.get(EdgeKeys.USER_KEY), call.getRequest()); + return EmptyObject.INSTANCE; + }); } /** @@ -939,17 +939,16 @@ private OpenemsAppInstance findInstanceByComponentId(String componentId) throws * * @param user the User * @param request the {@link UpdateAppConfigRequest} Request - * @return the Future JSON-RPC Response * @throws OpenemsNamedException on error */ - public UpdateAppConfig.Response handleUpdateAppConfigRequest(User user, UpdateAppConfig.Request request) - throws OpenemsNamedException { + public void handleUpdateAppConfigRequest(User user, UpdateAppConfig.Request request) throws OpenemsNamedException { final var appInstance = this.findInstanceByComponentId(request.componentId()); // update Component the old fashioned way if no app exists for the component if (appInstance == null) { - return this.updateComponentDirectly(user, request); + this.updateComponentDirectly(user, request); + return; } final var app = this.findAppByIdOrError(appInstance.appId); @@ -970,19 +969,16 @@ public UpdateAppConfig.Response handleUpdateAppConfigRequest(User user, UpdateAp // handleUpdateAppInstanceRequest Method var req = new UpdateAppInstance.Request(appInstance.instanceId, appInstance.alias, requestProperties); this.handleUpdateAppInstanceRequest(user, req); - return new UpdateAppConfig.Response(); } - private UpdateAppConfig.Response updateComponentDirectly(User user, UpdateAppConfig.Request from) - throws OpenemsNamedException { + private void updateComponentDirectly(User user, UpdateAppConfig.Request from) throws OpenemsNamedException { final var properties = from.properties(); final var componentUpdateProps = new ArrayList(); for (var key : properties.keySet()) { componentUpdateProps.add(new UpdateComponentConfigRequest.Property(key, properties.get(key))); } - final var updateRequest = new UpdateComponentConfigRequest(from.componentId(), componentUpdateProps); + final var updateRequest = new UpdateComponentConfig.Request(from.componentId(), componentUpdateProps); this.componentManager.handleUpdateComponentConfigRequest(user, updateRequest); - return new UpdateAppConfig.Response(); } /** @@ -1080,7 +1076,7 @@ private void updateAppManagerConfiguration(User user, List a var p = new Property("apps", this.getJsonAppsString(apps)); // user can be null using internal method this.componentManager.handleUpdateComponentConfigRequest(user, - new UpdateComponentConfigRequest(SINGLETON_COMPONENT_ID, Arrays.asList(p))); + new UpdateComponentConfig.Request(SINGLETON_COMPONENT_ID, Arrays.asList(p))); } private static void sortApps(List apps) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java index 46e6d79ebaa..b6e5e3dc5d4 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/ComponentUtilImpl.java @@ -34,6 +34,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.Component; import io.openems.common.utils.JsonUtils; @@ -522,12 +523,13 @@ public String[] getPreferredRelays(// } if (fallBackInARowRelays == null) { count = 0; - var startIndex = 1; + var startIndex = 0; for (var channelInfo : relayInfo.channels()) { + count++; if (!channelInfo.usingComponents().isEmpty() // || !channelInfo.disabledReasons().isEmpty()) { startIndex += count; - count = 1; + count = 0; } if (count >= numberOfRelays) { break; @@ -623,7 +625,7 @@ public synchronized void setSchedulerComponentIds(// } var ids = componentIds.stream().map(JsonPrimitive::new).collect(toJsonArray()); - final var request = new UpdateComponentConfigRequest(scheduler.getId(), List.of(// + final var request = new UpdateComponentConfig.Request(scheduler.getId(), List.of(// new UpdateComponentConfigRequest.Property("controllers.ids", ids) // )); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java index 45cc193805c..893047ff47b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/OpenemsAppInstance.java @@ -63,7 +63,7 @@ public static JsonSerializer serializer() { json -> new OpenemsAppInstance(// json.getString("appId"), // json.getString("alias"), // - json.getStringPath("instanceId").getAsUuid(), // + json.getUuid("instanceId"), // json.getJsonObject("properties"), // // TODO add optional methods json.getList("dependencies", Dependency.serializer())), // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java index acb45b0508c..dbdefa1c0cb 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/Dependency.java @@ -47,7 +47,7 @@ public static JsonSerializer serializer() { return jsonObjectSerializer(Dependency.class, // json -> new Dependency(// json.getString("key"), // - json.getStringPath("instanceId").getAsUuid()), // + json.getUuid("instanceId")), // obj -> JsonUtils.buildJsonObject() // .addProperty("key", obj.key) // .addProperty("instanceId", obj.instanceId.toString()) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java index d7e878ddc73..f6570d16def 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/ComponentAggregateTaskImpl.java @@ -19,10 +19,10 @@ import io.openems.common.exceptions.InvalidValueException; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; -import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.session.Language; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; @@ -296,7 +296,7 @@ private final boolean anyChanges() { } private void deleteComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { - this.componentManager.handleDeleteComponentConfigRequest(user, new DeleteComponentConfigRequest(comp.getId())); + this.componentManager.handleDeleteComponentConfigRequest(user, new DeleteComponentConfig.Request(comp.getId())); } private void createComponent(User user, EdgeConfig.Component comp) throws OpenemsNamedException { @@ -306,7 +306,7 @@ private void createComponent(User user, EdgeConfig.Component comp) throws Openem properties.add(new Property("alias", comp.getAlias())); this.componentManager.handleCreateComponentConfigRequest(user, - new CreateComponentConfigRequest(comp.getFactoryId(), properties)); + new CreateComponentConfig.Request(comp.getFactoryId(), properties)); } /** @@ -331,7 +331,7 @@ private void reconfigure(User user, EdgeConfig.Component myComp, EdgeConfig.Comp properties.add(new Property("alias", myComp.getAlias())); this.componentManager.handleUpdateComponentConfigRequest(user, - new UpdateComponentConfigRequest(actualComp.getId(), properties)); + new UpdateComponentConfig.Request(actualComp.getId(), properties)); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/PersistencePredictorAggregateTaskImpl.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/PersistencePredictorAggregateTaskImpl.java index 7ce66695a85..5b8dcc9fb38 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/PersistencePredictorAggregateTaskImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/dependency/aggregatetask/PersistencePredictorAggregateTaskImpl.java @@ -23,6 +23,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.ComponentManager; @@ -189,7 +190,7 @@ private static void updatePredictor(// existingChannels.removeAll(channelsToRemove); componentManager.handleUpdateComponentConfigRequest(user, - new UpdateComponentConfigRequest(predictor.id(), List.of(// + new UpdateComponentConfig.Request(predictor.id(), List.of(// new UpdateComponentConfigRequest.Property("channelAddresses", existingChannels.stream() // .map(JsonPrimitive::new) // .collect(toJsonArray())) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java index 786cdad77c8..07e51f38f60 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/Exp.java @@ -139,8 +139,46 @@ public static ArrayExpression array(Variable... variable) { */ public static StringExpression ifElse(BooleanExpression statement, StringExpression ifTrue, StringExpression ifFalse) { - return new StringExpression( - statement.expression() + " ? " + ifTrue.expression() + " : " + ifFalse.expression()); + return new StringExpression(ifElse(statement, ifTrue.expression(), ifFalse.expression())); + } + + /** + * Creates a combined {@link Variable} of the given {@link BooleanExpression} + * and {@link Variable}, which returns the first {@link Variable} if the given + * {@link BooleanExpression} returns true otherwise the second {@link Variable} + * gets returned. + * + * @param statement the {@link BooleanExpression} to determine which + * {@link Variable} should be used + * @param ifTrue the {@link Variable} to use when the statement returns true + * @param ifFalse the {@link Variable} to use when the statement returns false + * @return the final {@link Variable} + */ + public static Variable ifElse(BooleanExpression statement, Variable ifTrue, Variable ifFalse) { + return new Variable(ifElse(statement, ifTrue.variable(), ifFalse.variable())); + } + + /** + * Creates a combined {@link BooleanExpression} of the given + * {@link BooleanExpression} and {@link BooleanExpression}, which returns the + * first {@link BooleanExpression} if the given {@link BooleanExpression} + * returns true otherwise the second {@link BooleanExpression} gets returned. + * + * @param statement the {@link BooleanExpression} to determine which + * {@link BooleanExpression} should be used + * @param ifTrue the {@link BooleanExpression} to use when the statement + * returns true + * @param ifFalse the {@link BooleanExpression} to use when the statement + * returns false + * @return the final {@link BooleanExpression} + */ + public static BooleanExpression ifElse(BooleanExpression statement, BooleanExpression ifTrue, + BooleanExpression ifFalse) { + return new BooleanExpression(ifElse(statement, ifTrue.expression(), ifFalse.expression())); + } + + private static String ifElse(BooleanExpression statement, String ifTrue, String ifFalse) { + return "( " + statement.expression() + " ? " + ifTrue + " : " + ifFalse + ")"; } private Exp() { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java index 5856bcc3922..8497f43a0a6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/CheckboxBuilder.java @@ -1,13 +1,6 @@ package io.openems.edge.core.appmanager.formly.builder; -import com.google.gson.JsonObject; - -import io.openems.common.session.Language; -import io.openems.common.utils.JsonUtils; -import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.Nameable; -import io.openems.edge.core.appmanager.TranslationUtil; -import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; /** * A Builder for a Formly Checkbox. @@ -31,40 +24,10 @@ */ public final class CheckboxBuilder extends FormlyBuilder { - private JsonObject validation; - public CheckboxBuilder(Nameable property) { super(property); } - /** - * Requires the checkbox to be checked. - * - * @param l the language of the message - * @return this - */ - public CheckboxBuilder requireTrue(Language l) { - this.templateOptions.addProperty("pattern", "true"); - final var message = TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), - "formly.validation.requireChecked"); - this.getValidation().add("messages", JsonUtils.buildJsonObject() // - .addProperty("pattern", message) // - .build()); - - return this; - } - - private JsonObject getValidation() { - return this.validation = JsonFormlyUtil.single(this.validation); - } - - @Override - public JsonObject build() { - final var result = super.build(); - result.add("validation", this.validation); - return result; - } - @Override protected String getType() { return "checkbox"; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java index a4d88a8ef72..9f301b9ebe0 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FieldGroupBuilder.java @@ -45,7 +45,7 @@ protected String getType() { public JsonObject build() { final var object = super.build(); final var templateOptions = object.get("templateOptions").getAsJsonObject(); - templateOptions.remove("required"); + JsonUtils.getAsOptionalJsonObject(object, "expressionProperties") // .map(t -> t.remove("templateOptions.required")); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java index 8f9f2777b24..2f01ad0535b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/FormlyBuilder.java @@ -9,10 +9,13 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.OnlyIf; import io.openems.edge.core.appmanager.Self; +import io.openems.edge.core.appmanager.TranslationUtil; import io.openems.edge.core.appmanager.formly.DefaultValueOptions; import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.formly.enums.Wrappers; @@ -44,9 +47,10 @@ public abstract class FormlyBuilder> implements OnlyI protected final JsonObject jsonObject = new JsonObject(); protected final JsonObject templateOptions = new JsonObject(); - private JsonObject expressionProperties = null; + private JsonObject expressionProperties; private final List wrappers = new ArrayList<>(); - private JsonObject validators = null; + private JsonObject validators; + private JsonObject validation; protected FormlyBuilder(Nameable property) { this.setType(this.getType()); @@ -142,6 +146,23 @@ public final T isRequired(boolean isRequired) { return this.self(); } + /** + * Requires the checkbox to be checked. + * + * @param l the language of the message + * @return this + */ + public final T requireTrue(Language l) { + this.templateOptions.addProperty("pattern", "true"); + final var message = TranslationUtil.getTranslation(AbstractOpenemsApp.getTranslationBundle(l), + "formly.validation.requireChecked"); + this.getValidation().add("messages", JsonUtils.buildJsonObject() // + .addProperty("pattern", message) // + .build()); + + return this.self(); + } + public final T setLabel(String label) { if (label != null) { this.templateOptions.addProperty("label", label); @@ -158,7 +179,7 @@ public final T setDescription(String description) { private final T onlyShowIf(String expression) { this.getExpressionProperties().addProperty("templateOptions.required", expression); - this.jsonObject.addProperty("hideExpression", "!(" + expression + ")"); + this.getExpressionProperties().addProperty("hide", "!(" + expression + ")"); return this.self(); } @@ -332,6 +353,9 @@ public JsonObject build() { if (this.validators != null) { this.jsonObject.add("validators", this.validators); } + if (this.validation != null) { + this.jsonObject.add("validation", this.validation); + } return this.jsonObject; } @@ -345,6 +369,10 @@ protected final JsonObject getValidators() { return this.validators = JsonFormlyUtil.single(this.validators); } + protected final JsonObject getValidation() { + return this.validation = JsonFormlyUtil.single(this.validation); + } + @Override @SuppressWarnings("unchecked") public T self() { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java index 65e5eb0b1b9..0c4b9515766 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/InputBuilder.java @@ -9,7 +9,6 @@ import io.openems.edge.core.appmanager.AbstractOpenemsApp; import io.openems.edge.core.appmanager.Nameable; import io.openems.edge.core.appmanager.TranslationUtil; -import io.openems.edge.core.appmanager.formly.JsonFormlyUtil; import io.openems.edge.core.appmanager.formly.enums.InputType; import io.openems.edge.core.appmanager.formly.enums.Validation; import io.openems.edge.core.appmanager.formly.enums.Wrappers; @@ -48,7 +47,6 @@ */ public final class InputBuilder extends FormlyBuilder { - private JsonObject validation = null; private InputType type = InputType.TEXT; public InputBuilder(Nameable property) { @@ -236,14 +234,7 @@ public JsonObject build() { if (this.type != InputType.TEXT) { this.templateOptions.addProperty("type", this.type.getFormlyTypeName()); } - if (this.validation != null && this.validation.size() > 0) { - this.jsonObject.add("validation", this.validation); - } return super.build(); } - protected final JsonObject getValidation() { - return this.validation = JsonFormlyUtil.single(this.validation); - } - } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java index 31ba86a2347..b64dc40dc42 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/builder/SelectGroupBuilder.java @@ -69,6 +69,22 @@ public SelectGroupBuilder setMulti(boolean isMulti) { return this; } + /** + * Sets the text which gets displayed when not a single {@link OptionGroup} was + * added. + * + * @param text the text to display if not a single {@link OptionGroup} was added + * @return this + */ + public SelectGroupBuilder setMissingOptionsText(String text) { + if (text != null) { + this.templateOptions.addProperty("missingOptionsText", text); + } else { + this.templateOptions.remove("missingOptionsText"); + } + return this; + } + @Override public JsonObject build() { // wrap input field into a popup input @@ -82,10 +98,15 @@ public JsonObject build() { this.templateOptions.add("options", this.optionGroups.stream() // .map(OptionGroup::toJson) // .collect(toJsonArray())); + + // "reset" required property in popup input and only set it in final input + this.isRequired(false); + fieldGroup.setFieldGroup(JsonUtils.buildJsonArray() // .add(super.build()) // - .build()) // - .setDefaultValue(this.getDefaultValue()); + .build()); // + + fieldGroup.setDefaultValue(this.getDefaultValue()); fieldGroup.setPopupInput(this.property, DisplayType.OPTION_GROUP); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/BooleanExpression.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/BooleanExpression.java index 3b38534b051..133cb64152b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/BooleanExpression.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/BooleanExpression.java @@ -1,5 +1,6 @@ package io.openems.edge.core.appmanager.formly.expression; +import io.openems.edge.core.appmanager.formly.Exp; import io.openems.edge.core.appmanager.formly.enums.Operator; public record BooleanExpression(String expression) { @@ -39,4 +40,56 @@ public BooleanExpression and(BooleanExpression other) { return new BooleanExpression(this.expression() + " && " + other.expression()); } + /** + * Creates a combined {@link StringExpression} of the current + * {@link BooleanExpression} and {@link StringExpression}, which returns the + * first {@link StringExpression} if the given {@link BooleanExpression} returns + * true otherwise the second {@link StringExpression} gets returned. + * + * @param ifTrue the {@link StringExpression} to use when the statement returns + * true + * @param ifFalse the {@link StringExpression} to use when the statement returns + * false + * @return the final {@link StringExpression} + * + * @see Exp#ifElse(BooleanExpression, StringExpression, StringExpression) + */ + public StringExpression ifElse(StringExpression ifTrue, StringExpression ifFalse) { + return Exp.ifElse(this, ifTrue, ifFalse); + } + + /** + * Creates a combined {@link Variable} of the current {@link BooleanExpression} + * and {@link Variable}, which returns the first {@link Variable} if the given + * {@link BooleanExpression} returns true otherwise the second {@link Variable} + * gets returned. + * + * @param ifTrue the {@link Variable} to use when the statement returns true + * @param ifFalse the {@link Variable} to use when the statement returns false + * @return the final {@link Variable} + * + * @see Exp#ifElse(BooleanExpression, Variable, Variable) + */ + public Variable ifElse(Variable ifTrue, Variable ifFalse) { + return Exp.ifElse(this, ifTrue, ifFalse); + } + + /** + * Creates a combined {@link BooleanExpression} of the current + * {@link BooleanExpression} and {@link BooleanExpression}, which returns the + * first {@link BooleanExpression} if the given {@link BooleanExpression} + * returns true otherwise the second {@link BooleanExpression} gets returned. + * + * @param ifTrue the {@link BooleanExpression} to use when the statement + * returns true + * @param ifFalse the {@link BooleanExpression} to use when the statement + * returns false + * @return the final {@link BooleanExpression} + * + * @see Exp#ifElse(BooleanExpression, BooleanExpression, BooleanExpression) + */ + public BooleanExpression ifElse(BooleanExpression ifTrue, BooleanExpression ifFalse) { + return Exp.ifElse(this, ifTrue, ifFalse); + } + } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/Variable.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/Variable.java index 52baa9e3699..c1bdfbec819 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/Variable.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/formly/expression/Variable.java @@ -94,6 +94,15 @@ public BooleanExpression lowerThanEqual(Variable other) { return BooleanExpression.of(this, Operator.LTE, other); } + /** + * Checks if the current value of the variable is an array. + * + * @return the created {@link BooleanExpression} + */ + public BooleanExpression isArray() { + return new BooleanExpression("Array.isArray(" + this.variable + ")"); + } + /** * Uses this variable as an array. * diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java index 17c21023811..4beb91619fd 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/AddAppInstance.java @@ -8,10 +8,10 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonElementPath; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance.Request; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance.Response; @@ -107,7 +107,7 @@ public record Response(// public static JsonSerializer serializer() { return jsonObjectSerializer(Response.class, // json -> new Response(// - json.getElement("instance", OpenemsAppInstance.serializer()), // + json.getObject("instance", OpenemsAppInstance.serializer()), // json.getList("warnings", JsonElementPath::getAsString)), // obj -> JsonUtils.buildJsonObject() // .add("instance", obj.instance.toJsonObject()) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java index f3023bb117b..78654019c48 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/DeleteAppInstance.java @@ -8,10 +8,10 @@ import com.google.gson.JsonPrimitive; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonElementPath; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.jsonrpc.DeleteAppInstance.Request; import io.openems.edge.core.appmanager.jsonrpc.DeleteAppInstance.Response; @@ -75,7 +75,7 @@ public record Request(// public static JsonSerializer serializer() { return jsonObjectSerializer(DeleteAppInstance.Request.class, // json -> new DeleteAppInstance.Request(// - json.getStringPath("instanceId").getAsUuid()), // + json.getUuid("instanceId")), // obj -> JsonUtils.buildJsonObject() // .addProperty("instanceId", obj.instanceId().toString()) // .build()); diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java index 1263f5a5ed7..bd74d178b73 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApp.java @@ -13,10 +13,10 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.session.Language; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.flag.Flag; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java index 18f99677a19..cc3c31ebaf7 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppAssistant.java @@ -3,9 +3,9 @@ import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.AppAssistant; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.jsonrpc.GetAppAssistant.Request; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppDescriptor.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppDescriptor.java index 97922ed1d28..709dd7617cc 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppDescriptor.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppDescriptor.java @@ -3,9 +3,9 @@ import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.AppDescriptor; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.jsonrpc.GetAppDescriptor.Request; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java index 1163db1bab0..7422f767eda 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetAppInstances.java @@ -5,9 +5,9 @@ import java.util.List; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.jsonrpc.GetAppInstances.Request; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java index b25eacfc46f..7051e56bb12 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetApps.java @@ -1,6 +1,5 @@ package io.openems.edge.core.appmanager.jsonrpc; -import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.emptyObjectSerializer; import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; import java.util.List; @@ -9,14 +8,14 @@ import com.google.gson.JsonArray; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.session.Language; import io.openems.common.session.Role; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsApp; import io.openems.edge.core.appmanager.OpenemsAppInstance; -import io.openems.edge.core.appmanager.jsonrpc.GetApps.Request; import io.openems.edge.core.appmanager.jsonrpc.GetApps.Response; import io.openems.edge.core.appmanager.validator.Validator; @@ -63,7 +62,7 @@ * } * */ -public class GetApps implements EndpointRequestType { +public class GetApps implements EndpointRequestType { @Override public String getMethod() { @@ -71,8 +70,8 @@ public String getMethod() { } @Override - public JsonSerializer getRequestSerializer() { - return Request.serializer(); + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); } @Override @@ -80,19 +79,6 @@ public JsonSerializer getResponseSerializer() { return Response.serializer(); } - public record Request() { - - /** - * Returns a {@link JsonSerializer} for a {@link GetApps.Response}. - * - * @return the created {@link JsonSerializer} - */ - public static JsonSerializer serializer() { - return emptyObjectSerializer(Request::new); - } - - } - public record Response(// JsonArray apps // ) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetEstimatedConfiguration.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetEstimatedConfiguration.java index f2b10cf9853..a36d0adc37b 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetEstimatedConfiguration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/GetEstimatedConfiguration.java @@ -9,9 +9,9 @@ import com.google.gson.JsonObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.dependency.aggregatetask.AggregateTask; import io.openems.edge.core.appmanager.jsonrpc.GetEstimatedConfiguration.Request; import io.openems.edge.core.appmanager.jsonrpc.GetEstimatedConfiguration.Response; diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppConfig.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppConfig.java index 0253c0742a3..734b37acd58 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppConfig.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppConfig.java @@ -4,13 +4,12 @@ import com.google.gson.JsonObject; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; -import io.openems.common.jsonrpc.serialization.JsonSerializerUtil; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.jsonrpc.UpdateAppConfig.Request; -import io.openems.edge.core.appmanager.jsonrpc.UpdateAppConfig.Response; /** * Updates an {@link OpenemsAppInstance}. @@ -44,7 +43,7 @@ * } * */ -public class UpdateAppConfig implements EndpointRequestType { +public class UpdateAppConfig implements EndpointRequestType { @Override public String getMethod() { @@ -57,8 +56,8 @@ public JsonSerializer getRequestSerializer() { } @Override - public JsonSerializer getResponseSerializer() { - return Response.serializer(); + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); } public record Request(// @@ -84,19 +83,4 @@ public static JsonSerializer serializer() { } - public record Response(// - - ) { - - /** - * Returns a {@link JsonSerializer} for a {@link UpdateAppInstance.Response}. - * - * @return the created {@link JsonSerializer} - */ - public static JsonSerializer serializer() { - return JsonSerializerUtil.emptyObjectSerializer(Response::new); - } - - } - } diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java index 50facf512a4..c29dc6e8fe6 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/jsonrpc/UpdateAppInstance.java @@ -9,10 +9,10 @@ import com.google.gson.JsonObject; import com.google.gson.JsonPrimitive; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonElementPath; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.OpenemsAppInstance; import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance.Request; import io.openems.edge.core.appmanager.jsonrpc.UpdateAppInstance.Response; @@ -80,7 +80,7 @@ public record Request(// public static JsonSerializer serializer() { return jsonObjectSerializer(UpdateAppInstance.Request.class, // json -> new UpdateAppInstance.Request(// - json.getStringPath("instanceId").getAsUuid(), // + json.getUuid("instanceId"), // json.getString("alias"), // json.getJsonObject("properties")), // obj -> JsonUtils.buildJsonObject() // @@ -105,7 +105,7 @@ public record Response(// public static JsonSerializer serializer() { return jsonObjectSerializer(UpdateAppInstance.Response.class, // json -> new UpdateAppInstance.Response(// - json.getElement("instance", OpenemsAppInstance.serializer()), // + json.getObject("instance", OpenemsAppInstance.serializer()), // json.getList("warnings", JsonElementPath::getAsString)), // obj -> JsonUtils.buildJsonObject() // .add("instance", OpenemsAppInstance.serializer().serialize(obj.instance())) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties index 620bdbb678a..64cdbbe0d15 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_de.properties @@ -70,6 +70,7 @@ relay.defaultRelayContact = Standard relay.duplicatedRelayContactSelected = Doppelte Relais Kontakte ausgewählt relay.relayContactAlreadyUsed = Nicht verfügbar (belegt durch {0}) relay.notApproved = nicht Freigegeben +relay.missingOptions = Keine Relais verfügbar # formly formly.validation.requireChecked = Diese Checkbox ist erforderlich! diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties index 5441df63c3a..fab0fbaa9cc 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/translation_en.properties @@ -70,6 +70,7 @@ relay.defaultRelayContact = Default relay.duplicatedRelayContactSelected = Duplicated relay contact selected relay.relayContactAlreadyUsed = Not available (occupied by {0}) relay.notApproved = not approved +relay.missingOptions = No Relay available # formly formly.validation.requireChecked = This checkbox is required! diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/CheckRelayCountFilters.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/CheckRelayCountFilters.java index 0e75f8c0a8e..e76a96ac38f 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/CheckRelayCountFilters.java +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/CheckRelayCountFilters.java @@ -1,5 +1,7 @@ package io.openems.edge.core.appmanager.validator.relaycount; +import static java.util.Collections.emptyMap; + import java.util.Map; public class CheckRelayCountFilters { @@ -17,4 +19,14 @@ public static InjectableComponentConfig feneconHome(boolean onlyHighVoltageRelay )); } + /** + * Creates a {@link InjectableComponentConfig} for a + * {@link CheckRelayCountFilter} which filters device hardware contacts. + * + * @return the {@link InjectableComponentConfig} + */ + public static InjectableComponentConfig deviceHardware() { + return new InjectableComponentConfig(DeviceHardwareFilter.COMPONENT_NAME, emptyMap()); + } + } \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/DeviceHardwareFilter.java b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/DeviceHardwareFilter.java new file mode 100644 index 00000000000..90f1a276e7e --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/appmanager/validator/relaycount/DeviceHardwareFilter.java @@ -0,0 +1,71 @@ +package io.openems.edge.core.appmanager.validator.relaycount; + +import static java.util.stream.Collectors.toUnmodifiableSet; + +import java.util.Objects; +import java.util.stream.Stream; + +import org.osgi.service.component.annotations.Activate; +import org.osgi.service.component.annotations.Component; +import org.osgi.service.component.annotations.Reference; +import org.osgi.service.component.annotations.ServiceScope; + +import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; +import io.openems.common.session.Language; +import io.openems.edge.app.common.props.RelayProps; +import io.openems.edge.app.common.props.RelayProps.RelayContactFilter; +import io.openems.edge.core.appmanager.AppManagerUtil; +import io.openems.edge.core.appmanager.ConfigurationTarget; +import io.openems.edge.core.appmanager.OpenemsAppCategory; + +@Component(// + name = DeviceHardwareFilter.COMPONENT_NAME, // + scope = ServiceScope.PROTOTYPE // +) +public class DeviceHardwareFilter implements CheckRelayCountFilter { + + public static final String COMPONENT_NAME = "CheckRelayCount.Filter.DeviceHardware"; + + private final AppManagerUtil appManagerUtil; + + @Activate + public DeviceHardwareFilter(// + @Reference AppManagerUtil appManagerUtil // + ) { + super(); + this.appManagerUtil = appManagerUtil; + } + + @Override + public RelayContactFilter apply() { + final var deviceHardwareInstances = this.appManagerUtil + .getInstantiatedAppsByCategories(OpenemsAppCategory.OPENEMS_DEVICE_HARDWARE); + + if (deviceHardwareInstances.isEmpty()) { + return RelayProps.emptyFilter(); + } + + final var dependencyInstances = deviceHardwareInstances.stream() // + .flatMap(t -> t.dependencies.stream()) // + .map(t -> this.appManagerUtil.findInstanceById(t.instanceId).orElse(null)) // + .filter(Objects::nonNull) // + .toList(); + + final var blacklistedComponents = dependencyInstances.stream() // + .flatMap(instance -> { + try { + final var configuration = this.appManagerUtil.getAppConfiguration(ConfigurationTarget.UPDATE, + instance, Language.DEFAULT); + + return configuration.getComponents().stream().map(t -> t.getId()); + } catch (OpenemsNamedException e) { + return Stream.empty(); + } + }) // + .collect(toUnmodifiableSet()); + + return RelayContactFilter.create() // + .withComponentFilter(t -> !blacklistedComponents.contains(t.id())); + } + +} \ No newline at end of file diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/ComponentManagerImpl.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/ComponentManagerImpl.java index 0c963faf1ea..388cb863f6c 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/ComponentManagerImpl.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/ComponentManagerImpl.java @@ -45,13 +45,13 @@ import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.jsonrpc.base.GenericJsonrpcResponseSuccess; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; import io.openems.common.jsonrpc.request.GetEdgeConfigRequest; -import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; import io.openems.common.jsonrpc.response.GetEdgeConfigResponse; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.session.Language; import io.openems.common.session.Role; import io.openems.common.types.ChannelAddress; @@ -333,42 +333,39 @@ public void buildJsonApiRoutes(JsonApiBuilder builder) { GetEdgeConfigRequest.from(t.getRequest())); }); - builder.handleRequest(CreateComponentConfigRequest.METHOD, endpoint -> { + builder.handleRequest(new CreateComponentConfig(), endpoint -> { endpoint.setDescription(""" Handles a CreateComponentConfigRequest. """) // .setGuards(EdgeGuards.roleIsAtleastFromBackend(Role.INSTALLER), // EdgeGuards.roleIsAtleastNotFromBackend(Role.ADMIN)); }, t -> { - this.handleCreateComponentConfigRequest(t.get(EdgeKeys.USER_KEY), // - CreateComponentConfigRequest.from(t.getRequest())); + this.handleCreateComponentConfigRequest(t.get(EdgeKeys.USER_KEY), t.getRequest()); - return new GenericJsonrpcResponseSuccess(t.getRequest().getId()); + return EmptyObject.INSTANCE; }); - builder.handleRequest(UpdateComponentConfigRequest.METHOD, endpoint -> { + builder.handleRequest(new UpdateComponentConfig(), endpoint -> { endpoint.setDescription(""" Handles a UpdateComponentConfigRequest. """) // .setGuards(EdgeGuards.roleIsAtleast(Role.OWNER)); }, t -> { - this.handleUpdateComponentConfigRequest(t.get(EdgeKeys.USER_KEY), // - UpdateComponentConfigRequest.from(t.getRequest())); + this.handleUpdateComponentConfigRequest(t.get(EdgeKeys.USER_KEY), t.getRequest()); - return new GenericJsonrpcResponseSuccess(t.getRequest().getId()); + return EmptyObject.INSTANCE; }); - builder.handleRequest(DeleteComponentConfigRequest.METHOD, endpoint -> { + builder.handleRequest(new DeleteComponentConfig(), endpoint -> { endpoint.setDescription(""" Handles a DeleteComponentConfigRequest. """) // .setGuards(EdgeGuards.roleIsAtleastFromBackend(Role.INSTALLER), // EdgeGuards.roleIsAtleastNotFromBackend(Role.ADMIN)); }, t -> { - this.handleDeleteComponentConfigRequest(t.get(EdgeKeys.USER_KEY), // - DeleteComponentConfigRequest.from(t.getRequest())); + this.handleDeleteComponentConfigRequest(t.get(EdgeKeys.USER_KEY), t.getRequest()); - return new GenericJsonrpcResponseSuccess(t.getRequest().getId()); + return EmptyObject.INSTANCE; }); builder.handleRequest(ChannelExportXlsxRequest.METHOD, endpoint -> { @@ -502,11 +499,11 @@ private GetEdgeConfigResponse handleGetEdgeConfigRequest(User user, GetEdgeConfi } @Override - public void handleCreateComponentConfigRequest(User user, CreateComponentConfigRequest request) + public void handleCreateComponentConfigRequest(User user, CreateComponentConfig.Request request) throws OpenemsNamedException { // Get Component-ID from Request String componentId = null; - for (Property property : request.getProperties()) { + for (Property property : request.properties()) { if (property.getName().equals("id")) { componentId = JsonUtils.getAsString(property.getValue()); } @@ -527,31 +524,31 @@ public void handleCreateComponentConfigRequest(User user, CreateComponentConfigR throw new OpenemsException("A Component with id [" + componentId + "] is already existing!"); } try { - config = this.cm.createFactoryConfiguration(request.getFactoryPid(), null); + config = this.cm.createFactoryConfiguration(request.factoryPid(), null); } catch (IOException e) { e.printStackTrace(); throw OpenemsError.GENERIC.exception("Unable create Configuration for Factory-ID [" - + request.getFactoryPid() + "]. " + e.getClass().getSimpleName() + ": " + e.getMessage()); + + request.factoryPid() + "]. " + e.getClass().getSimpleName() + ": " + e.getMessage()); } } else { // Singleton? try { - config = this.cm.getConfiguration(request.getFactoryPid(), null); + config = this.cm.getConfiguration(request.factoryPid(), null); } catch (IOException e) { e.printStackTrace(); throw OpenemsError.GENERIC.exception("Unable to get Configurations for Factory-PID [" - + request.getFactoryPid() + "]. " + e.getClass().getSimpleName() + ": " + e.getMessage()); + + request.factoryPid() + "]. " + e.getClass().getSimpleName() + ": " + e.getMessage()); } if (config.getProperties() != null) { throw new OpenemsException( - "A Singleton Component for PID [" + request.getFactoryPid() + "] is already existing!"); + "A Singleton Component for PID [" + request.factoryPid() + "] is already existing!"); } } // Create map with configuration attributes Dictionary properties = new Hashtable<>(); - for (Property property : request.getProperties()) { + for (Property property : request.properties()) { var value = JsonUtils.getAsBestType(property.getValue()); if (value instanceof Object[] os && os.length == 0) { value = new String[0]; @@ -564,19 +561,19 @@ public void handleCreateComponentConfigRequest(User user, CreateComponentConfigR this.applyConfiguration(user, config, properties); } catch (IOException | IllegalArgumentException e) { e.printStackTrace(); - throw OpenemsError.EDGE_UNABLE_TO_CREATE_CONFIG.exception(request.getFactoryPid(), e.getMessage()); + throw OpenemsError.EDGE_UNABLE_TO_CREATE_CONFIG.exception(request.factoryPid(), e.getMessage()); } } @Override - public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigRequest request) + public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfig.Request request) throws OpenemsNamedException { - var config = this.getExistingConfigForId(request.getComponentId()); + var config = this.getExistingConfigForId(request.componentId()); // Create map with changed configuration attributes var properties = config.getProperties(); if (properties == null) { - throw OpenemsError.EDGE_UNABLE_TO_APPLY_CONFIG.exception(request.getComponentId(), + throw OpenemsError.EDGE_UNABLE_TO_APPLY_CONFIG.exception(request.componentId(), config.getPid() + ": Properties is 'null'"); } @@ -588,7 +585,7 @@ public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigR } } - for (Property property : request.getProperties()) { + for (Property property : request.properties()) { // do not allow certain properties to be updated, like pid and service.pid if (!EdgeConfig.ignorePropertyKey(property.getName())) { var jValue = property.getValue(); @@ -611,20 +608,20 @@ public void handleUpdateComponentConfigRequest(User user, UpdateComponentConfigR this.applyConfiguration(user, config, properties); } catch (IOException e) { e.printStackTrace(); - throw OpenemsError.EDGE_UNABLE_TO_APPLY_CONFIG.exception(request.getComponentId(), e.getMessage()); + throw OpenemsError.EDGE_UNABLE_TO_APPLY_CONFIG.exception(request.componentId(), e.getMessage()); } } @Override - public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfigRequest request) + public void handleDeleteComponentConfigRequest(User user, DeleteComponentConfig.Request request) throws OpenemsNamedException { - var config = this.getExistingConfigForId(request.getComponentId()); + var config = this.getExistingConfigForId(request.componentId()); try { config.delete(); } catch (IOException e) { e.printStackTrace(); - throw OpenemsError.EDGE_UNABLE_TO_DELETE_CONFIG.exception(request.getComponentId(), e.getMessage()); + throw OpenemsError.EDGE_UNABLE_TO_DELETE_CONFIG.exception(request.componentId(), e.getMessage()); } } diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/DefaultConfigurationWorker.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/DefaultConfigurationWorker.java index c884719aea5..897cd076220 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/DefaultConfigurationWorker.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/DefaultConfigurationWorker.java @@ -15,10 +15,10 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; -import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest.Property; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; /** * This Worker checks if certain OpenEMS-Components are configured and - if not @@ -178,7 +178,7 @@ protected void createConfiguration(AtomicBoolean defaultConfigurationFailed, Str .map(p -> p.getName() + ":" + p.getValue().toString()) // .collect(Collectors.joining(", "))); this.parent.handleCreateComponentConfigRequest(null /* no user */, - new CreateComponentConfigRequest(factoryPid, properties)); + new CreateComponentConfig.Request(factoryPid, properties)); } catch (OpenemsNamedException e) { this.parent.logError(this.log, "Unable to create Component configuration for Factory [" + factoryPid + "]: " + e.getMessage()); @@ -204,7 +204,7 @@ protected void updateConfiguration(AtomicBoolean defaultConfigurationFailed, Str .collect(Collectors.joining(", "))); this.parent.handleUpdateComponentConfigRequest(null /* no user */, - new UpdateComponentConfigRequest(componentId, properties)); + new UpdateComponentConfig.Request(componentId, properties)); } catch (OpenemsNamedException e) { this.parent.logError(this.log, "Unable to update Component configuration for Component [" + componentId + "]: " + e.getMessage()); @@ -225,7 +225,7 @@ protected void deleteConfiguration(AtomicBoolean defaultConfigurationFailed, Str this.parent.logInfo(this.log, "Deleting Component [" + componentId + "]"); this.parent.handleDeleteComponentConfigRequest(null /* no user */, - new DeleteComponentConfigRequest(componentId)); + new DeleteComponentConfig.Request(componentId)); } catch (OpenemsNamedException e) { this.parent.logError(this.log, "Unable to delete Component [" + componentId + "]: " + e.getMessage()); e.printStackTrace(); diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsx.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsx.java new file mode 100644 index 00000000000..b6541c7cdcb --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/ChannelExportXlsx.java @@ -0,0 +1,63 @@ +package io.openems.edge.core.componentmanager.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.type.Base64RequestType; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; +import io.openems.edge.core.componentmanager.jsonrpc.ChannelExportXlsx.Request; + +/** + * Exports Channels with current value and metadata to an Excel (xlsx) file. + * + *

+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "channelExportXlsx",
+ *   "params": {
+ *   	"componentId": string
+ *   }
+ * }
+ * 
+ */ +public class ChannelExportXlsx implements EndpointRequestType { + + @Override + public String getMethod() { + return "channelExportXlsx"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Base64RequestType.serializer(); + } + + public static record Request(// + String componentId // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link AddAppInstance.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, // + json -> new Request(// + json.getString("componentId")), + obj -> JsonUtils.buildJsonObject() // + .addProperty("componentId", obj.componentId()) // + .build()); + } + + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetAllComponentFactories.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetAllComponentFactories.java index 8d9301f7cb2..36124551ff8 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetAllComponentFactories.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetAllComponentFactories.java @@ -1,18 +1,16 @@ package io.openems.edge.core.componentmanager.jsonrpc; -import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.emptyObjectSerializer; import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; import com.google.gson.JsonObject; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; -import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; -import io.openems.edge.core.componentmanager.jsonrpc.GetAllComponentFactories.Request; import io.openems.edge.core.componentmanager.jsonrpc.GetAllComponentFactories.Response; -public class GetAllComponentFactories implements EndpointRequestType { +public class GetAllComponentFactories implements EndpointRequestType { @Override public String getMethod() { @@ -20,8 +18,8 @@ public String getMethod() { } @Override - public JsonSerializer getRequestSerializer() { - return Request.serializer(); + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); } @Override @@ -29,19 +27,6 @@ public JsonSerializer getResponseSerializer() { return Response.serializer(); } - public record Request() { - - /** - * Returns a {@link JsonSerializer} for a {@link AddAppInstance.Request}. - * - * @return the created {@link JsonSerializer} - */ - public static JsonSerializer serializer() { - return emptyObjectSerializer(Request::new); - } - - } - // TODO change to proper type public record Response(JsonObject factories) { diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannel.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannel.java index 516c1908170..eb6feb14ba5 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannel.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannel.java @@ -2,9 +2,9 @@ import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; import io.openems.edge.core.componentmanager.jsonrpc.GetChannel.Request; import io.openems.edge.core.componentmanager.jsonrpc.GetChannel.Response; @@ -59,7 +59,7 @@ public record Response(ChannelRecord channel) { */ public static JsonSerializer serializer() { return jsonObjectSerializer(GetChannel.Response.class, json -> { - return new Response(json.getElement("channel", ChannelRecord.serializer())); + return new Response(json.getObject("channel", ChannelRecord.serializer())); }, obj -> { return JsonUtils.buildJsonObject() // .add("channel", ChannelRecord.serializer().serialize(obj.channel())) // diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannelsOfComponent.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannelsOfComponent.java index b632c12ab2e..5db58cbb582 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannelsOfComponent.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetChannelsOfComponent.java @@ -13,11 +13,11 @@ import io.openems.common.channel.Level; import io.openems.common.channel.PersistencePriority; import io.openems.common.channel.Unit; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.types.OpenemsType; import io.openems.common.types.OptionsEnum; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; import io.openems.edge.core.componentmanager.jsonrpc.GetChannelsOfComponent.Request; import io.openems.edge.core.componentmanager.jsonrpc.GetChannelsOfComponent.Response; diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetDigitalInputChannelsOfComponents.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetDigitalInputChannelsOfComponents.java index cb820d51cc8..06a34a5c45d 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetDigitalInputChannelsOfComponents.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetDigitalInputChannelsOfComponents.java @@ -9,10 +9,10 @@ import com.google.gson.JsonPrimitive; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonElementPath; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.common.type.Tuple; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; import io.openems.edge.core.componentmanager.jsonrpc.GetChannelsOfComponent.ChannelRecord; diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetPropertiesOfFactory.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetPropertiesOfFactory.java index e942097bd79..7b2ebdfa543 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetPropertiesOfFactory.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetPropertiesOfFactory.java @@ -5,9 +5,9 @@ import com.google.gson.JsonArray; import com.google.gson.JsonObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; import io.openems.edge.core.componentmanager.jsonrpc.GetPropertiesOfFactory.Request; import io.openems.edge.core.componentmanager.jsonrpc.GetPropertiesOfFactory.Response; diff --git a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetStateChannelsOfComponent.java b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetStateChannelsOfComponent.java index 181233e0eb5..9f63b58b5dc 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetStateChannelsOfComponent.java +++ b/io.openems.edge.core/src/io/openems/edge/core/componentmanager/jsonrpc/GetStateChannelsOfComponent.java @@ -4,9 +4,9 @@ import java.util.List; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.appmanager.jsonrpc.AddAppInstance; import io.openems.edge.core.componentmanager.jsonrpc.GetChannelsOfComponent.ChannelRecord; import io.openems.edge.core.componentmanager.jsonrpc.GetStateChannelsOfComponent.Request; diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/Inet4AddressWithSubnetmask.java b/io.openems.edge.core/src/io/openems/edge/core/host/Inet4AddressWithSubnetmask.java index c32944ec160..891917f64ad 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/Inet4AddressWithSubnetmask.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/Inet4AddressWithSubnetmask.java @@ -1,10 +1,19 @@ package io.openems.edge.core.host; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; + import java.net.Inet4Address; import java.util.Objects; +import com.google.gson.JsonPrimitive; + import io.openems.common.exceptions.OpenemsException; +import io.openems.common.exceptions.OpenemsRuntimeException; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.jsonrpc.serialization.StringParser; import io.openems.common.utils.InetAddressUtils; +import io.openems.common.utils.JsonUtils; /** * Helper class for wrapping an IPv4 address together with its subnetmask in @@ -12,6 +21,83 @@ */ public class Inet4AddressWithSubnetmask { + public static class StringParserInet4AddressWithSubnetmask implements StringParser { + + @Override + public Inet4AddressWithSubnetmask parse(String value) { + try { + return Inet4AddressWithSubnetmask.fromString(value); + } catch (OpenemsException e) { + throw new OpenemsRuntimeException(e); + } + } + + @Override + public ExampleValues getExample() { + try { + final var address = "127.0.0.1/16"; + return new ExampleValues<>(address, Inet4AddressWithSubnetmask.fromString(address)); + } catch (OpenemsException e) { + throw new OpenemsRuntimeException(e); + } + } + + } + + /** + * Returns a {@link JsonSerializer} for a {@link Inet4Address}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer inet4AddressSerializer() { + return jsonSerializer(Inet4Address.class, json -> { + return json.getAsStringParsed(new StringParserInet4Address()); + }, obj -> { + return new JsonPrimitive(obj.getHostAddress()); + }); + } + + public static class StringParserInet4Address implements StringParser { + + @Override + public Inet4Address parse(String value) { + try { + return InetAddressUtils.parseOrError(value); + } catch (OpenemsException e) { + throw new OpenemsRuntimeException(e); + } + } + + @Override + public ExampleValues getExample() { + try { + final var address = "255.255.255.0"; + return new ExampleValues<>(address, InetAddressUtils.parseOrError(address)); + } catch (OpenemsException e) { + throw new OpenemsRuntimeException(e); + } + } + + } + + public static class StringParserCidrFromSubnetmask implements StringParser { + + @Override + public Integer parse(String value) { + try { + return getCidrFromSubnetmask(InetAddressUtils.parseOrError(value)); + } catch (OpenemsException e) { + throw new OpenemsRuntimeException(e); + } + } + + @Override + public ExampleValues getExample() { + return new ExampleValues<>("255.255.255.0", 24); + } + + } + /** * Parse a string in the form "192.168.100.100/24" to an IPv4 address. Label is * set to an empty string with this factory method. @@ -76,6 +162,27 @@ public static int getCidrFromSubnetmask(Inet4Address subnetmask) throws OpenemsE return cidr; } + /** + * Returns a {@link JsonSerializer} for a {@link Inet4AddressWithSubnetmask}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Inet4AddressWithSubnetmask.class, json -> { + return new Inet4AddressWithSubnetmask(// + json.getString("label"), // + json.getStringParsed("address", new Inet4AddressWithSubnetmask.StringParserInet4Address()), // + json.getStringParsed("subnetmask", new Inet4AddressWithSubnetmask.StringParserCidrFromSubnetmask()) // + ); + }, obj -> { + return JsonUtils.buildJsonObject() // + .addProperty("label", obj.getLabel()) // + .addProperty("address", obj.getInet4Address().getHostAddress()) // + .addProperty("subnetmask", obj.getSubnetmaskAsString()) // + .build(); + }); + } + private final String label; private final Inet4Address inet4Address; private final int subnetmask; diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java index 0ce1a666cf6..7ff6c82a53a 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkConfiguration.java @@ -1,19 +1,45 @@ package io.openems.edge.core.host; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonObject; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toMap; + +import java.util.Map; import java.util.Map.Entry; -import java.util.TreeMap; +import java.util.function.Function; import com.google.gson.JsonObject; +import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; public class NetworkConfiguration { public static final String PATTERN_INET4ADDRESS = "(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)"; - private final TreeMap> interfaces; + /** + * Returns a {@link JsonSerializer} for a {@link NetworkConfiguration}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(NetworkConfiguration.class, json -> { + return new NetworkConfiguration(json.getJsonObjectPath("interfaces").collectStringKeys( + mapping(t -> NetworkInterface.serializer(t.getKey()).deserializePath(t.getValue()), + toMap(NetworkInterface::getName, Function.identity())))); + }, obj -> { + return JsonUtils.buildJsonObject() // + .add("interfaces", obj.getInterfaces().entrySet().stream() // + .collect(toJsonObject(Entry::getKey, + e -> NetworkInterface.serializer(e.getKey()).serialize(e.getValue())))) // + .build(); + }); + } + + private final Map> interfaces; - public NetworkConfiguration(TreeMap> interfaces) { + public NetworkConfiguration(Map> interfaces) { this.interfaces = interfaces; } @@ -55,7 +81,7 @@ public JsonObject toJson() { * * @return a map of network interfaces per name */ - public TreeMap> getInterfaces() { + public Map> getInterfaces() { return this.interfaces; } diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkInterface.java b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkInterface.java index 4cce05f0a93..0fded69c91c 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/NetworkInterface.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/NetworkInterface.java @@ -1,5 +1,6 @@ package io.openems.edge.core.host; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; import static io.openems.common.utils.JsonUtils.buildJsonArray; import static io.openems.common.utils.JsonUtils.buildJsonObject; import static io.openems.common.utils.JsonUtils.getAsInet4Address; @@ -18,6 +19,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; +import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.types.ConfigurationProperty; import io.openems.common.utils.JsonUtils; @@ -38,6 +40,52 @@ public class NetworkInterface
{ } } + /** + * Returns a {@link JsonSerializer} for a {@link NetworkInterface}. + * + * @param name the name of the interface + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer> serializer(String name) { + return jsonObjectSerializer(json -> { + return new NetworkInterface(// + name, // + json.getOptionalBoolean("dhcp").map(ConfigurationProperty::of) + .orElseGet(ConfigurationProperty::asNotSet), // + json.getOptionalBoolean("linkLocalAddressing").map(ConfigurationProperty::of) + .orElseGet(ConfigurationProperty::asNotSet), // + ConfigurationProperty.of(json.getStringParsedOrNull("gateway", + new Inet4AddressWithSubnetmask.StringParserInet4Address())), // + ConfigurationProperty.of(json.getStringParsedOrNull("dns", + new Inet4AddressWithSubnetmask.StringParserInet4Address())), // + ConfigurationProperty.of(json.getSet("addresses", Inet4AddressWithSubnetmask.serializer())), // + json.getOptionalInt("metric").map(ConfigurationProperty::of) // + .orElseGet(ConfigurationProperty::asNotSet), // + null); + }, obj -> { + return JsonUtils.buildJsonObject() // + .onlyIf(obj.getDhcp().isSetAndNotNull(), t -> { + t.addProperty("dhcp", obj.getDhcp().getValue()); + }) // + .onlyIf(obj.getLinkLocalAddressing().isSetAndNotNull(), t -> { + t.addProperty("dhcp", obj.getLinkLocalAddressing().getValue()); + }) // + .onlyIf(obj.getGateway().isSetAndNotNull(), t -> { + t.addProperty("dhcp", obj.getGateway().getValue().getHostAddress()); + }) // + .onlyIf(!obj.getMetric().isNull(), t -> { + t.addProperty("metric", obj.getMetric().getValue()); + }) // + .onlyIf(!obj.getDns().isNull(), t -> { + t.addProperty("dns", obj.getDns().getValue().getHostAddress()); + }) // + .onlyIf(obj.getAddresses().isSet(), t -> { + t.add("addresses", Inet4AddressWithSubnetmask.serializer().toSetSerializer() + .serialize(obj.getAddresses().getValue())); + }).build(); + }); + } + /** * Parses a JsonObject to a {@link java.net.NetworkInterface} object. * @@ -134,7 +182,8 @@ public NetworkInterface(String name, // ConfigurationProperty dns, // ConfigurationProperty> addresses, // ConfigurationProperty metric, // - A attachment) throws OpenemsException { + A attachment // + ) { this.name = name; this.dhcp = dhcp; this.linkLocalAddressing = linkLocalAddressing; diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java index fdde5cc2159..f4b6c6832b5 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemMac.java @@ -13,7 +13,7 @@ import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandRequest; import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandResponse; import io.openems.edge.core.host.jsonrpc.ExecuteSystemRestartRequest; -import io.openems.edge.core.host.jsonrpc.GetNetworkInfo.Response; +import io.openems.edge.core.host.jsonrpc.GetNetworkInfo; import io.openems.edge.core.host.jsonrpc.SetNetworkConfigRequest; public class OperatingSystemMac implements OperatingSystem { @@ -59,7 +59,7 @@ public CompletableFuture getOperatingSystemVersion() { } @Override - public Response getNetworkInfo() throws OpenemsNamedException { + public GetNetworkInfo.Response getNetworkInfo() throws OpenemsNamedException { throw new NotImplementedException("This request is not implemented for mac"); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java index cb719210824..98efd56be46 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/OperatingSystemWindows.java @@ -13,8 +13,8 @@ import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandRequest; import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommandResponse; import io.openems.edge.core.host.jsonrpc.ExecuteSystemRestartRequest; +import io.openems.edge.core.host.jsonrpc.GetNetworkInfo; import io.openems.edge.core.host.jsonrpc.SetNetworkConfigRequest; -import io.openems.edge.core.host.jsonrpc.GetNetworkInfo.Response; /** * OperatingSystem implementation for Windows. @@ -60,7 +60,7 @@ public List getSystemIPs() throws OpenemsNamedException { } @Override - public Response getNetworkInfo() throws OpenemsNamedException { + public GetNetworkInfo.Response getNetworkInfo() throws OpenemsNamedException { throw new NotImplementedException("This request is not implemented for Windows"); } diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommand.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommand.java new file mode 100644 index 00000000000..f0374af37e9 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemCommand.java @@ -0,0 +1,122 @@ +package io.openems.edge.core.host.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonArray; + +import java.util.Arrays; +import java.util.Optional; + +import com.google.gson.JsonPrimitive; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonElementPath; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommand.Request; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemCommand.Response; + +public class ExecuteSystemCommand implements EndpointRequestType { + + @Override + public String getMethod() { + return "executeSystemCommand"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Request(// + String command, // + boolean runInBackground, // + int timeoutSeconds, // + Optional username, // + Optional password // + ) { + + /** + * Returns a {@link JsonSerializer} for a {@link ExecuteSystemCommand.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(ExecuteSystemCommand.Request.class, // + json -> new ExecuteSystemCommand.Request(// + json.getString("command"), // + json.getOptionalBoolean("runInBackground") // + .orElse(false), // + json.getIntOrDefault("timeoutSeconds", 5), // + json.getOptionalString("username"), // + json.getOptionalString("password")), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("command", obj.command()) // + .addProperty("runInBackground", obj.runInBackground()) // + .addProperty("timeoutSeconds", obj.timeoutSeconds()) // + .addPropertyIfNotNull("username", obj.username().orElse(null)) // + .addPropertyIfNotNull("password", obj.password().orElse(null)) // + .build()); + } + + /** + * Creates a {@link ExecuteSystemCommand.Request} which runs in the background + * without authentication. + * + * @param command the command of the {@link ExecuteSystemCommand.Request} + * @return the created {@link ExecuteSystemCommand.Request} + */ + public static Request runInBackgroundWithoutAuthentication(String command) { + return withoutAuthentication(command, true, 5); + } + + /** + * Creates a {@link ExecuteSystemCommand.Request} without authentication. + * + * @param command the command of the + * {@link ExecuteSystemCommand.Request} + * @param runInBackground if the command should run in background + * @param timeoutSeconds the command timeout + * @return the created {@link ExecuteSystemCommand.Request} + */ + public static Request withoutAuthentication(// + String command, // + boolean runInBackground, // + int timeoutSeconds // + ) { + return new Request(command, runInBackground, timeoutSeconds, Optional.empty(), Optional.empty()); + } + + } + + public record Response(String[] stdout, String[] stderr, int exitcode) { + + /** + * Returns a {@link JsonSerializer} for a {@link ExecuteSystemCommand.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(ExecuteSystemCommand.Response.class, // + json -> new ExecuteSystemCommand.Response(// + json.getArray("stdout", String[]::new, JsonElementPath::getAsString), // + json.getArray("stderr", String[]::new, JsonElementPath::getAsString), // + json.getInt("exitcode")), + obj -> JsonUtils.buildJsonObject() // + .add("stdout", Arrays.stream(obj.stdout()) // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) // + .add("stderr", Arrays.stream(obj.stderr()) // + .map(JsonPrimitive::new) // + .collect(toJsonArray())) // + .addProperty("exitcode", obj.exitcode()) // + .build()); + } + + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestart.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestart.java new file mode 100644 index 00000000000..c93fbd05a3c --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemRestart.java @@ -0,0 +1,72 @@ +package io.openems.edge.core.host.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemRestart.Request; + +/** + * Represents a JSON-RPC Request to execute a system restart. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "executeSystemRestart",
+ *   "params": {
+ *   	"type": "SOFT" | "HARD"
+ *   }
+ * }
+ * 
+ */ +public class ExecuteSystemRestart implements EndpointRequestType { + + @Override + public String getMethod() { + return "executeSystemRestart"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return ExecuteSystemCommand.Response.serializer(); + } + + public record Request(// + Type type // + ) { + + public enum Type { + /** + * SOFT: restart only the Java OpenEMS Edge process. + */ + SOFT, + /** + * HARD: reboot the device. + */ + HARD; + } + + /** + * Returns a {@link JsonSerializer} for a {@link ExecuteSystemRestart.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(ExecuteSystemRestart.Request.class, // + json -> new ExecuteSystemRestart.Request(// + json.getEnum("type", Type.class)), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("type", obj.type()) // + .build()); + } + + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemUpdate.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemUpdate.java new file mode 100644 index 00000000000..25d1da4bdd6 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/ExecuteSystemUpdate.java @@ -0,0 +1,60 @@ + +package io.openems.edge.core.host.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.host.jsonrpc.ExecuteSystemUpdate.Request; + +/** + * Executes a System Update. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "executeSystemUpdate",
+ *   "params": {
+ *     "isDebug": boolean
+ *   }
+ * }
+ * 
+ */ +public class ExecuteSystemUpdate implements EndpointRequestType { + + @Override + public String getMethod() { + return "executeSystemUpdate"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(boolean isDebug) { + + /** + * Returns a {@link JsonSerializer} for a {@link ExecuteSystemUpdate}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(Request.class, // + json -> new Request(json.getBoolean("isDebug")), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("isDebug", obj.isDebug()) // + .build()); + } + + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfig.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfig.java new file mode 100644 index 00000000000..decc29d79fe --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkConfig.java @@ -0,0 +1,46 @@ +package io.openems.edge.core.host.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.edge.core.host.NetworkConfiguration; +import io.openems.edge.core.host.jsonrpc.GetNetworkConfig.Response; + +public class GetNetworkConfig implements EndpointRequestType { + + @Override + public String getMethod() { + return "getNetworkConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Response(NetworkConfiguration config) { + + /** + * Returns a {@link JsonSerializer} for a {@link SetNetworkConfig.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + final var serializer = NetworkConfiguration.serializer(); + return jsonSerializer(GetNetworkConfig.Response.class, json -> { + return new GetNetworkConfig.Response(serializer.deserializePath(json)); + }, obj -> { + return serializer.serialize(obj.config()); + }); + } + + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkInfo.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkInfo.java index dac17f9aaf7..176dd552303 100644 --- a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkInfo.java +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetNetworkInfo.java @@ -1,38 +1,25 @@ package io.openems.edge.core.host.jsonrpc; -import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.emptyObjectSerializer; import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; import java.net.Inet4Address; import java.net.UnknownHostException; import java.util.List; +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; import io.openems.common.jsonrpc.serialization.JsonSerializer; import io.openems.common.utils.JsonUtils; -import io.openems.edge.common.jsonapi.EndpointRequestType; import io.openems.edge.core.host.Inet4AddressWithSubnetmask; -import io.openems.edge.core.host.jsonrpc.GetNetworkInfo.Request; import io.openems.edge.core.host.jsonrpc.GetNetworkInfo.Response; -public class GetNetworkInfo implements EndpointRequestType { +public class GetNetworkInfo implements EndpointRequestType { @Override public String getMethod() { return "getNetworkInfo"; } - public record Request() { - - /** - * Returns a {@link JsonSerializer} for a {@link GetNetworkInfo.Request}. - * - * @return the created {@link JsonSerializer} - */ - public static JsonSerializer serializer() { - return emptyObjectSerializer(Request::new); - } - } - public record NetworkInfoWrapper(String hardwareInterface, List ips) { /** @@ -142,8 +129,8 @@ public static JsonSerializer serializer() { } @Override - public JsonSerializer getRequestSerializer() { - return Request.serializer(); + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); } @Override diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateState.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateState.java new file mode 100644 index 00000000000..c24ec0dab30 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/GetSystemUpdateState.java @@ -0,0 +1,327 @@ + +package io.openems.edge.core.host.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.emptyObjectSerializer; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonSerializer; + +import java.io.PrintWriter; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicInteger; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.gson.JsonObject; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.types.SemanticVersion; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.host.SystemUpdateHandler; +import io.openems.edge.core.host.jsonrpc.GetSystemUpdateState.Response; + +/** + * Gets the System Update State. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "getSystemUpdateState",
+ *   "params": {
+ *   }
+ * }
+ * 
+ */ +public class GetSystemUpdateState implements EndpointRequestType { + + @Override + public String getMethod() { + return "getSystemUpdateState"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return EmptyObject.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return Response.serializer(); + } + + public record Response(SystemUpdateState updateState) { + + /** + * Returns a {@link JsonSerializer} for a {@link GetSystemUpdateState.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonSerializer(GetSystemUpdateState.Response.class, // + json -> new GetSystemUpdateState.Response(SystemUpdateState.serializer().deserializePath(json)), // + obj -> SystemUpdateState.serializer().serialize(obj.updateState())); + } + + public record SystemUpdateState(// + Running running, // + Available available, // + Updated updated, // + Unknown unknown // + ) { + + /** + * Creates a SystemUpdateState which indicates a running state. + * + * @param updateState the current state + * @return the created {@link SystemUpdateState} + */ + public static SystemUpdateState isRunning(UpdateState updateState) { + return new SystemUpdateState(new SystemUpdateState.Running(updateState), null, null, null); + } + + /** + * Creates a SystemUpdateState which indicates that a update is available. + * + * @param currentVersion the current version + * @param latestVersion the latest version to update to + * @return the created {@link SystemUpdateState} + */ + public static SystemUpdateState available(// + SemanticVersion currentVersion, // + SemanticVersion latestVersion // + ) { + return new SystemUpdateState(null, new SystemUpdateState.Available(currentVersion, latestVersion), null, + null); + } + + /** + * Returns a {@link JsonSerializer} for a {@link GetSystemUpdateState.Response}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SystemUpdateState.class, // + json -> { + final var updated = json.getNullableJsonElementPath("updated") + .getAsObjectOrNull(Updated.serializer()); + final var available = json.getNullableJsonElementPath("available") + .getAsObjectOrNull(Available.serializer()); + final var running = json.getNullableJsonElementPath("running") + .getAsObjectOrNull(Running.serializer()); + final var unknown = json.getNullableJsonElementPath("unknown") + .getAsObjectOrNull(Unknown.serializer()); + + return new SystemUpdateState(running, available, updated, unknown); + }, obj -> { + return JsonUtils.buildJsonObject() // + .onlyIf(obj.updated() != null, t -> { + t.add("updated", Updated.serializer().serialize(obj.updated())); + }) // + .onlyIf(obj.available() != null, t -> { + t.add("available", Available.serializer().serialize(obj.available())); + }) // + .onlyIf(obj.running() != null, t -> { + t.add("running", Running.serializer().serialize(obj.running())); + }) // + .onlyIf(obj.unknown() != null, t -> { + t.add("unknown", Unknown.serializer().serialize(obj.unknown())); + }) // + .build(); + }); + } + + public record Unknown() { + + /** + * Returns a {@link JsonSerializer} for a + * {@link GetSystemUpdateState.Response.SystemUpdateState.Unknown}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return emptyObjectSerializer(SystemUpdateState.Unknown::new); + } + + } + + public record Updated(SemanticVersion version) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link GetSystemUpdateState.Response.SystemUpdateState.Updated}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SystemUpdateState.Updated.class, // + json -> new GetSystemUpdateState.Response.SystemUpdateState.Updated(// + json.getSemanticVersion("version")), // + obj -> JsonUtils.buildJsonObject() // + .addProperty("version", obj.version().toString()) // + .build()); + } + + } + + public record Available(// + SemanticVersion currentVersion, // + SemanticVersion latestVersion // + ) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link GetSystemUpdateState.Response.SystemUpdateState.Available}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SystemUpdateState.Available.class, // + json -> new SystemUpdateState.Available(// + json.getSemanticVersion("currentVersion"), // + json.getSemanticVersion("latestVersion")), + obj -> JsonUtils.buildJsonObject() // + .addProperty("currentVersion", obj.currentVersion().toString()) // + .addProperty("latestVersion", obj.latestVersion().toString()) // + .build()); + } + + } + + public record Running(UpdateState updateState) { + + /** + * Returns a {@link JsonSerializer} for a + * {@link GetSystemUpdateState.Response.SystemUpdateState.Running}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SystemUpdateState.Running.class, // + json -> new SystemUpdateState.Running(new UpdateState()), + obj -> obj.updateState.toJsonObject()); + } + + } + + } + + } + + public static class UpdateState { + private final Logger log = LoggerFactory.getLogger(SystemUpdateHandler.class); + + private final AtomicBoolean isRunning = new AtomicBoolean(false); + private final AtomicInteger percentCompleted = new AtomicInteger(0); + private final List logs = new ArrayList<>(); + private boolean debugMode = false; + + public UpdateState() { + this.reset(); + } + + public void setRunning(boolean isRunning) { + this.isRunning.set(isRunning); + } + + public boolean isRunning() { + return this.isRunning.get(); + } + + public void setPercentCompleted(int percentCompleted) { + this.percentCompleted.set(percentCompleted); + } + + public synchronized void setDebugMode(boolean debugMode) { + this.debugMode = debugMode; + } + + /** + * Adds a line to the log. + * + * @param line the line + */ + public void addLog(String line) { + synchronized (this.log) { + this.log.info("System-Update: " + line); + if (this.debugMode) { + this.logs.add(line); + } + } + } + + /** + * Adds a {@link Exception} to the log. + * + * @param e the {@link Exception} + */ + public void addLog(Exception e) { + var sw = new StringWriter(); + var pw = new PrintWriter(sw); + e.printStackTrace(pw); + this.addLog(pw.toString()); + } + + /** + * Adds a {@link ExecuteSystemCommandResponse} with a label to the log. + * + * @param label the label + * @param response the {@link ExecuteSystemCommandResponse} + */ + public void addLog(String label, ExecuteSystemCommand.Response response) { + synchronized (this.log) { + var stdout = response.stdout(); + if (stdout.length > 0) { + this.addLog(label + ": STDOUT"); + for (var line : stdout) { + this.addLog(label + ": " + line); + } + } + var stderr = response.stderr(); + if (stderr.length > 0) { + this.addLog(label + ": STDERR"); + for (var line : stderr) { + this.addLog(label + ": " + line); + } + } + if (response.exitcode() == 0) { + this.addLog(label + ": FINISHED SUCCESSFULLY"); + } else { + this.addLog(label + ": FINISHED WITH ERROR CODE [" + response.exitcode() + "]"); + } + } + } + + protected JsonObject toJsonObject() { + var logs = JsonUtils.buildJsonArray(); + synchronized (this.log) { + for (String log : this.logs) { + logs.add(log); + } + } + return JsonUtils.buildJsonObject() // + .add("running", JsonUtils.buildJsonObject() // + .addProperty("percentCompleted", this.percentCompleted.get()) // + .add("logs", logs.build()) // + .build()) // + .build(); + } + + /** + * Resets the {@link UpdateState} object. + */ + public synchronized void reset() { + this.isRunning.set(false); + this.percentCompleted.set(0); + synchronized (this.log) { + this.logs.clear(); + } + } + } + +} diff --git a/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/SetNetworkConfig.java b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/SetNetworkConfig.java new file mode 100644 index 00000000000..b6e71a48508 --- /dev/null +++ b/io.openems.edge.core/src/io/openems/edge/core/host/jsonrpc/SetNetworkConfig.java @@ -0,0 +1,81 @@ +package io.openems.edge.core.host.jsonrpc; + +import static io.openems.common.jsonrpc.serialization.JsonSerializerUtil.jsonObjectSerializer; +import static io.openems.common.utils.JsonUtils.toJsonObject; +import static java.util.stream.Collectors.mapping; +import static java.util.stream.Collectors.toList; + +import java.util.List; + +import io.openems.common.jsonrpc.serialization.EmptyObject; +import io.openems.common.jsonrpc.serialization.EndpointRequestType; +import io.openems.common.jsonrpc.serialization.JsonSerializer; +import io.openems.common.utils.JsonUtils; +import io.openems.edge.core.host.NetworkInterface; +import io.openems.edge.core.host.jsonrpc.SetNetworkConfig.Request; + +/** + * Updates the current network configuration. + * + *
+ * {
+ *   "jsonrpc": "2.0",
+ *   "id": "UUID",
+ *   "method": "setNetworkConfig",
+ *   "params": {
+ *   "interfaces": {
+ *     [name: string]: {
+ *       "dhcp"?: boolean,
+ *       "linkLocalAddressing"?: boolean,
+ *       "gateway"?: string,
+ *       "dns"?: string,
+ *       "addresses"?: [{
+ *         "label": string,
+ *         "address": string,
+ *         "subnetmask": string
+ *       }]
+ *     }
+ *   }
+ * }
+ * 
+ */ +public class SetNetworkConfig implements EndpointRequestType { + + @Override + public String getMethod() { + return "setNetworkConfig"; + } + + @Override + public JsonSerializer getRequestSerializer() { + return Request.serializer(); + } + + @Override + public JsonSerializer getResponseSerializer() { + return EmptyObject.serializer(); + } + + public record Request(List> networkInterfaces) { + + /** + * Returns a {@link JsonSerializer} for a {@link SetNetworkConfig.Request}. + * + * @return the created {@link JsonSerializer} + */ + public static JsonSerializer serializer() { + return jsonObjectSerializer(SetNetworkConfig.Request.class, json -> { + return new SetNetworkConfig.Request(json.collectStringKeys(mapping(t -> { + return NetworkInterface.serializer(t.getKey()).deserializePath(t.getValue()); + }, toList()))); + }, obj -> { + return JsonUtils.buildJsonObject() // + .add("interfaces", obj.networkInterfaces().stream() // + .collect(toJsonObject(i -> i.getName(), i -> i.toJson()))) // + .build(); + }); + } + + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/app/common/props/RelayContactFilterTest.java b/io.openems.edge.core/test/io/openems/edge/app/common/props/RelayContactFilterTest.java new file mode 100644 index 00000000000..054da22915b --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/app/common/props/RelayContactFilterTest.java @@ -0,0 +1,96 @@ +package io.openems.edge.app.common.props; + +import static java.util.Collections.emptyList; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.BiPredicate; +import java.util.function.Function; +import java.util.function.Predicate; + +import org.junit.Before; +import org.junit.Test; + +import io.openems.edge.app.common.props.RelayProps.RelayContactFilter; +import io.openems.edge.common.channel.BooleanWriteChannel; +import io.openems.edge.io.api.DigitalOutput; + +public class RelayContactFilterTest { + + private RelayContactFilter relayContactFilter; + + @Before + public void setUp() { + final Predicate componentFilter = t -> true; + final Function componentAliasMapper = t -> t.alias(); + final BiPredicate channelFilter = (t, u) -> true; + final BiFunction channelAliasMapper = (t, u) -> t.alias(); + final BiFunction> disabledReasons = (t, u) -> emptyList(); + + this.relayContactFilter = RelayContactFilter.create() // + .withComponentFilter(componentFilter) // + .withComponentAliasMapper(componentAliasMapper) // + .withChannelFilter(channelFilter) // + .withChannelAliasMapper(channelAliasMapper) // + .withDisabledReasons(disabledReasons); + + assertEquals(componentFilter, this.relayContactFilter.componentFilter()); + assertEquals(componentAliasMapper, this.relayContactFilter.componentAliasMapper()); + assertEquals(channelFilter, this.relayContactFilter.channelFilter()); + assertEquals(channelAliasMapper, this.relayContactFilter.channelAliasMapper()); + assertEquals(disabledReasons, this.relayContactFilter.disabledReasons()); + } + + @Test + public void testWithComponentFilter() { + final Predicate componentFilter = t -> true; + assertNotEquals(componentFilter, this.relayContactFilter.componentFilter()); + + final var relayContactFilter = this.relayContactFilter.withComponentFilter(componentFilter); + + assertEquals(componentFilter, relayContactFilter.componentFilter()); + } + + @Test + public void testWithComponentAliasMapper() { + final Function componentAliasMapper = t -> t.alias(); + assertNotEquals(componentAliasMapper, this.relayContactFilter.componentAliasMapper()); + + final var relayContactFilter = this.relayContactFilter.withComponentAliasMapper(componentAliasMapper); + + assertEquals(componentAliasMapper, relayContactFilter.componentAliasMapper()); + } + + @Test + public void testWithChannelFilter() { + final BiPredicate channelFilter = (t, u) -> true; + assertNotEquals(channelFilter, this.relayContactFilter.channelFilter()); + + final var relayContactFilter = this.relayContactFilter.withChannelFilter(channelFilter); + + assertEquals(channelFilter, relayContactFilter.channelFilter()); + } + + @Test + public void testWithChannelAliasMapper() { + final BiFunction channelAliasMapper = (t, u) -> t.alias(); + assertNotEquals(channelAliasMapper, this.relayContactFilter.channelAliasMapper()); + + final var relayContactFilter = this.relayContactFilter.withChannelAliasMapper(channelAliasMapper); + + assertEquals(channelAliasMapper, relayContactFilter.channelAliasMapper()); + } + + @Test + public void testWithDisabledReasons() { + final BiFunction> disabledReasons = (t, u) -> emptyList(); + assertNotEquals(disabledReasons, this.relayContactFilter.disabledReasons()); + + final var relayContactFilter = this.relayContactFilter.withDisabledReasons(disabledReasons); + + assertEquals(disabledReasons, relayContactFilter.disabledReasons()); + } + +} diff --git a/io.openems.edge.core/test/io/openems/edge/app/evcs/TestEvcsCluster.java b/io.openems.edge.core/test/io/openems/edge/app/evcs/TestEvcsCluster.java index 06378d223fc..9d942f8e85b 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/evcs/TestEvcsCluster.java +++ b/io.openems.edge.core/test/io/openems/edge/app/evcs/TestEvcsCluster.java @@ -18,8 +18,8 @@ import com.google.common.collect.Lists; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.CreateComponentConfig; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.AppManagerTestBundle; @@ -116,22 +116,22 @@ public void testReinstallationWhenMoreOrEqualOfTwoEvcsExist() throws Exception { assertEquals(2, this.appManagerTestBundle.sut.getInstantiatedApps().size()); this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Evcs.Keba.KeContact", Lists.newArrayList(// + new CreateComponentConfig.Request("Evcs.Keba.KeContact", Lists.newArrayList(// new UpdateComponentConfigRequest.Property("id", "evcs0"), // new UpdateComponentConfigRequest.Property("ip", "1.1.1.1") // ))); this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Controller.Evcs", Lists.newArrayList(// + new CreateComponentConfig.Request("Controller.Evcs", Lists.newArrayList(// new UpdateComponentConfigRequest.Property("id", "ctrlEvcs0"), // new UpdateComponentConfigRequest.Property("evcs.id", "evcs0") // ))); this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Evcs.Keba.KeContact", Lists.newArrayList(// + new CreateComponentConfig.Request("Evcs.Keba.KeContact", Lists.newArrayList(// new UpdateComponentConfigRequest.Property("id", "evcs1"), // new UpdateComponentConfigRequest.Property("ip", "1.1.1.2") // ))); this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Controller.Evcs", Lists.newArrayList(// + new CreateComponentConfig.Request("Controller.Evcs", Lists.newArrayList(// new UpdateComponentConfigRequest.Property("id", "ctrlEvcs1"), // new UpdateComponentConfigRequest.Property("evcs.id", "evcs1") // ))); @@ -168,7 +168,7 @@ public void testClusterWasAlreadyExisting() throws Exception { final var clusterId = "evcsCluster0"; this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Evcs.Cluster.PeakShaving", Lists.newArrayList(// + new CreateComponentConfig.Request("Evcs.Cluster.PeakShaving", Lists.newArrayList(// new UpdateComponentConfigRequest.Property("id", clusterId), // new UpdateComponentConfigRequest.Property("enabled", false), // new UpdateComponentConfigRequest.Property("evcs.ids", JsonUtils.buildJsonArray() // diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java index 7cae58845f1..2beebcb7c5a 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome10Gen2.java @@ -122,4 +122,21 @@ public static final JsonObject fullSettings() { .addProperty("SHADOW_MANAGEMENT_DISABLED", false) // .build(); } + + /** + * Gets a {@link JsonObject} with the minimum settings for a + * {@link FeneconHome10}. + * + * @return the settings object + */ + public static final JsonObject minSettings() { + return JsonUtils.buildJsonObject() // + .addProperty("SAFETY_COUNTRY", "GERMANY") // + .addProperty("MAX_FEED_IN_POWER", 1000) // + .addProperty("FEED_IN_SETTING", "LAGGING_0_95") // + .addProperty("HAS_EMERGENCY_RESERVE", false) // + .addProperty("SHADOW_MANAGEMENT_DISABLED", false) // + .build(); + } + } diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java index 7fb5c681fed..c7b5b7051cc 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome20.java @@ -190,4 +190,26 @@ public static final JsonObject fullSettings() { .build(); } + /** + * Gets a {@link JsonObject} with the minimum settings for a + * {@link FeneconHome}. + * + * @return the settings object + */ + public static final JsonObject minSettings() { + return JsonUtils.buildJsonObject() // + .addProperty("SAFETY_COUNTRY", "GERMANY") // + .addProperty("FEED_IN_TYPE", FeedInType.DYNAMIC_LIMITATION) // + .addProperty("MAX_FEED_IN_POWER", 1000) // + .addProperty("FEED_IN_SETTING", "LAGGING_0_95") // + .addProperty("HAS_AC_METER", false) // + .addProperty("HAS_PV_1", false) // + .addProperty("HAS_PV_2", false) // + .addProperty("HAS_PV_3", false) // + .addProperty("HAS_PV_4", false) // + .addProperty("HAS_EMERGENCY_RESERVE", false) // + .addProperty("SHADOW_MANAGEMENT_DISABLED", false) // + .build(); + } + } diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java index f698c3a218f..9a8074f4d67 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30.java @@ -347,4 +347,28 @@ public static final JsonObject fullSettingsWithoutEmergencyReserve() { .build(); } + /** + * Gets a {@link JsonObject} with the minimum settings for a + * {@link FeneconHome}. + * + * @return the settings object + */ + public static final JsonObject minSettings() { + return JsonUtils.buildJsonObject() // + .addProperty("SAFETY_COUNTRY", "GERMANY") // + .addProperty("FEED_IN_TYPE", FeedInType.DYNAMIC_LIMITATION) // + .addProperty("MAX_FEED_IN_POWER", 1000) // + .addProperty("FEED_IN_SETTING", "LAGGING_0_95") // + .addProperty("HAS_AC_METER", false) // + .addProperty("HAS_PV_1", false) // + .addProperty("HAS_PV_2", false) // + .addProperty("HAS_PV_3", false) // + .addProperty("HAS_PV_4", false) // + .addProperty("HAS_PV_5", false) // + .addProperty("HAS_PV_6", false) // + .addProperty("HAS_EMERGENCY_RESERVE", false) // + .addProperty("SHADOW_MANAGEMENT_DISABLED", false) // + .build(); + } + } diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java index c67423251bc..a285b2e0791 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHome30DefaultRelays.java @@ -6,7 +6,7 @@ import org.junit.Before; import org.junit.Test; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.heat.CombinedHeatAndPower; import io.openems.edge.app.heat.HeatPump; @@ -95,7 +95,7 @@ public void testDefaultRelayValuesThresholdControl() throws Exception { private final OpenemsAppInstance createFullHomeWithDummyIo() throws Exception { final var instance = TestFeneconHome30.createFullHome30(this.appManagerTestBundle, DUMMY_ADMIN); this.appManagerTestBundle.componentManger.handleDeleteComponentConfigRequest(DUMMY_ADMIN, - new DeleteComponentConfigRequest("io0")); + new DeleteComponentConfig.Request("io0")); final var dummyRelay = new DummyCustomInputOutput("io0", "RELAY", 1, 8); this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay.id()); ((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay); diff --git a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java index 7127e6b227c..75b3fb17adb 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java +++ b/io.openems.edge.core/test/io/openems/edge/app/integratedsystem/TestFeneconHomeDefaultRelays.java @@ -6,7 +6,7 @@ import org.junit.Before; import org.junit.Test; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.app.heat.CombinedHeatAndPower; import io.openems.edge.app.heat.HeatPump; @@ -91,7 +91,7 @@ public void testDefaultRelayValuesThresholdControl() throws Exception { private final OpenemsAppInstance createFullHomeWithDummyIo() throws Exception { final var instance = TestFeneconHome10.createFullHome(this.appManagerTestBundle, DUMMY_ADMIN); this.appManagerTestBundle.componentManger.handleDeleteComponentConfigRequest(DUMMY_ADMIN, - new DeleteComponentConfigRequest("io0")); + new DeleteComponentConfig.Request("io0")); final var dummyRelay = new DummyCustomInputOutput("io0", "RELAY", 1, 4); this.appManagerTestBundle.cm.getOrCreateEmptyConfiguration(dummyRelay.id()); ((DummyPseudoComponentManager) this.appManagerTestBundle.componentManger).addComponent(dummyRelay); diff --git a/io.openems.edge.core/test/io/openems/edge/app/timeofusetariff/TestTibber.java b/io.openems.edge.core/test/io/openems/edge/app/timeofusetariff/TestTibber.java index cb4227e2309..cbed6373448 100644 --- a/io.openems.edge.core/test/io/openems/edge/app/timeofusetariff/TestTibber.java +++ b/io.openems.edge.core/test/io/openems/edge/app/timeofusetariff/TestTibber.java @@ -21,8 +21,9 @@ import com.google.gson.JsonNull; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.AppManagerTestBundle; import io.openems.edge.core.appmanager.AppManagerTestBundle.PseudoComponentManagerFactory; @@ -123,7 +124,7 @@ public void testSetTokenValue() throws Exception { assertEquals("xxx", value.getAsString()); this.appManagerTestBundle.componentManger.handleUpdateComponentConfigRequest(DUMMY_ADMIN, - new UpdateComponentConfigRequest(response.instance().properties + new UpdateComponentConfig.Request(response.instance().properties .get(Tibber.Property.TIME_OF_USE_TARIFF_PROVIDER_ID.name()).getAsString(), List.of(new UpdateComponentConfigRequest.Property("accessToken", "")))); @@ -150,7 +151,7 @@ public void testUnsetFilterValue() throws Exception { assertEquals("randomInitialFilter", value.getAsString()); this.appManagerTestBundle.componentManger.handleUpdateComponentConfigRequest(DUMMY_ADMIN, - new UpdateComponentConfigRequest( + new UpdateComponentConfig.Request( response.instance().properties.get(Tibber.Property.TIME_OF_USE_TARIFF_PROVIDER_ID.name()) .getAsString(), List.of(new UpdateComponentConfigRequest.Property(Tibber.Property.ACCESS_TOKEN.name(), @@ -165,7 +166,7 @@ public void testUnsetFilterValue() throws Exception { private void createPredictor() throws Exception { this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Predictor.PersistenceModel", List.of(// + new CreateComponentConfig.Request("Predictor.PersistenceModel", List.of(// new UpdateComponentConfigRequest.Property("id", "predictor0"), // new UpdateComponentConfigRequest.Property("channelAddresses", JsonUtils.buildJsonArray()// .build()) // diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java index dc8384768c9..5f5bb05e7f7 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/AppManagerTestBundle.java @@ -5,6 +5,7 @@ import static io.openems.common.utils.JsonUtils.toJsonArray; import static io.openems.common.utils.ReflectionUtils.setAttributeViaReflection; import static io.openems.common.utils.ReflectionUtils.setStaticAttributeViaReflection; +import static io.openems.edge.common.test.DummyUser.DUMMY_ADMIN; import static java.util.stream.Collectors.joining; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -671,4 +672,18 @@ private final void modifyWithCurrentConfig() throws OpenemsNamedException { } } + /** + * Tries to install the provided app with the minimal available configuration. + * + * @param app the app to install + * @throws InterruptedException if the current thread was interruptedwhile + * waiting + * @throws ExecutionException if this future completed exceptionally + * @throws OpenemsNamedException on installation error + */ + public void tryInstallWithMinConfig(OpenemsApp app) throws OpenemsNamedException { + this.sut.handleAddAppInstanceRequest(DUMMY_ADMIN, + new AddAppInstance.Request(app.getAppId(), "key", "alias", Apps.getMinConfig(app.getAppId()))); + } + } diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java index 6bf3134da74..41f85d94f76 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/Apps.java @@ -8,6 +8,8 @@ import org.osgi.service.cm.ConfigurationAdmin; import org.osgi.service.component.ComponentContext; +import com.google.gson.JsonObject; + import io.openems.edge.app.TestADependencyToC; import io.openems.edge.app.TestBDependencyToC; import io.openems.edge.app.TestC; @@ -17,6 +19,7 @@ import io.openems.edge.app.api.ModbusRtuApiReadWrite; import io.openems.edge.app.api.ModbusTcpApiReadOnly; import io.openems.edge.app.api.ModbusTcpApiReadWrite; +import io.openems.edge.app.api.MqttApi; import io.openems.edge.app.api.RestJsonApiReadOnly; import io.openems.edge.app.api.RestJsonApiReadWrite; import io.openems.edge.app.api.TimedataInfluxDb; @@ -26,6 +29,7 @@ import io.openems.edge.app.ess.PowerPlantController; import io.openems.edge.app.ess.PrepareBatteryExtension; import io.openems.edge.app.evcs.AlpitronicEvcs; +import io.openems.edge.app.evcs.DezonyEvcs; import io.openems.edge.app.evcs.EvcsCluster; import io.openems.edge.app.evcs.HardyBarthEvcs; import io.openems.edge.app.evcs.IesKeywattEvcs; @@ -33,6 +37,8 @@ import io.openems.edge.app.evcs.WebastoNextEvcs; import io.openems.edge.app.evcs.WebastoUniteEvcs; import io.openems.edge.app.evcs.readonly.MennekesEvcsReadOnly; +import io.openems.edge.app.hardware.IoGpio; +import io.openems.edge.app.hardware.KMtronic8Channel; import io.openems.edge.app.heat.CombinedHeatAndPower; import io.openems.edge.app.heat.HeatPump; import io.openems.edge.app.heat.HeatingElement; @@ -42,12 +48,17 @@ import io.openems.edge.app.integratedsystem.FeneconHome20; import io.openems.edge.app.integratedsystem.FeneconHome30; import io.openems.edge.app.integratedsystem.FeneconHome6; +import io.openems.edge.app.integratedsystem.TestFeneconHome10; +import io.openems.edge.app.integratedsystem.TestFeneconHome10Gen2; +import io.openems.edge.app.integratedsystem.TestFeneconHome20; +import io.openems.edge.app.integratedsystem.TestFeneconHome30; import io.openems.edge.app.integratedsystem.fenecon.commercial.FeneconCommercial92; import io.openems.edge.app.loadcontrol.ManualRelayControl; import io.openems.edge.app.loadcontrol.ThresholdControl; import io.openems.edge.app.meter.CarloGavazziMeter; import io.openems.edge.app.meter.DiscovergyMeter; import io.openems.edge.app.meter.JanitzaMeter; +import io.openems.edge.app.meter.KdkMeter; import io.openems.edge.app.meter.MicrocareSdm630Meter; import io.openems.edge.app.meter.PhoenixContactMeter; import io.openems.edge.app.meter.PqPlusMeter; @@ -398,6 +409,16 @@ public static final ModbusTcpApiReadWrite modbusTcpApiReadWrite(AppManagerTestBu return app(t, ModbusTcpApiReadWrite::new, "App.Api.ModbusTcp.ReadWrite"); } + /** + * Test method for creating a {@link MqttApi}. + * + * @param t the {@link AppManagerTestBundle} + * @return the {@link OpenemsApp} instance + */ + public static final MqttApi mqttApi(AppManagerTestBundle t) { + return app(t, MqttApi::new, "App.Api.Mqtt"); + } + /** * Test method for creating a {@link ModbusRtuApiReadOnly}. * @@ -440,6 +461,26 @@ public static final RestJsonApiReadWrite restJsonApiReadWrite(AppManagerTestBund // Evcs + /** + * Test method for creating a {@link RestJsonApiReadOnly}. + * + * @param t the {@link AppManagerTestBundle} + * @return the {@link OpenemsApp} instance + */ + public static final AlpitronicEvcs alpitronic(AppManagerTestBundle t) { + return app(t, AlpitronicEvcs::new, "App.Evcs.Alpitronic"); + } + + /** + * Test method for creating a {@link RestJsonApiReadOnly}. + * + * @param t the {@link AppManagerTestBundle} + * @return the {@link OpenemsApp} instance + */ + public static final DezonyEvcs dezony(AppManagerTestBundle t) { + return app(t, DezonyEvcs::new, "App.Evcs.Dezony"); + } + /** * Test method for creating a {@link RestJsonApiReadOnly}. * @@ -530,6 +571,28 @@ public static final EvcsCluster evcsCluster(AppManagerTestBundle t) { return app(t, EvcsCluster::new, "App.Evcs.Cluster"); } + // Hardware + + /** + * Test method for creating a {@link KMtronic8Channel}. + * + * @param t the {@link AppManagerTestBundle} + * @return the {@link OpenemsApp} instance + */ + public static final KMtronic8Channel kmtronic8Channel(AppManagerTestBundle t) { + return app(t, KMtronic8Channel::new, "App.Hardware.KMtronic8Channel"); + } + + /** + * Test method for creating a {@link IoGpio}. + * + * @param t the {@link AppManagerTestBundle} + * @return the {@link OpenemsApp} instance + */ + public static final IoGpio ioGpio(AppManagerTestBundle t) { + return app(t, IoGpio::new, "App.Hardware.IoGpio"); + } + // Heat /** @@ -618,6 +681,16 @@ public static final SocomecMeter socomecMeter(AppManagerTestBundle t) { return app(t, SocomecMeter::new, "App.Meter.Socomec"); } + /** + * Test method for creating a {@link KdkMeter}. + * + * @param t the {@link AppManagerTestBundle} + * @return the {@link OpenemsApp} instance + */ + public static final KdkMeter kdkMeter(AppManagerTestBundle t) { + return app(t, KdkMeter::new, "App.Meter.Kdk"); + } + /** * Test method for creating a {@link DiscoveregyMeter}. * @@ -804,6 +877,25 @@ public static final Limiter14a limiter14a(AppManagerTestBundle t) { return app(t, Limiter14a::new, "App.Ess.Limiter14a"); } + /** + * Gets the minimum configuration of an app for easily creating instances in + * tests. + * + * @param appId the id of the {@link OpenemsApp} + * @return the configuration to create an instance + */ + public static JsonObject getMinConfig(String appId) { + return switch (appId) { + case "App.FENECON.Home" -> TestFeneconHome10.minSettings(); + case "App.FENECON.Home.20" -> TestFeneconHome20.minSettings(); + case "App.FENECON.Home.30" -> TestFeneconHome30.minSettings(); + case "App.FENECON.Home6" -> TestFeneconHome10Gen2.minSettings(); + case "App.FENECON.Home10.Gen2" -> TestFeneconHome10Gen2.minSettings(); + case "App.FENECON.Home15" -> TestFeneconHome10Gen2.minSettings(); + default -> new JsonObject(); + }; + } + private static final T app(AppManagerTestBundle t, DefaultAppConstructor constructor, String appId) { return constructor.create(t.componentManger, AppManagerTestBundle.getComponentContext(appId), t.cm, t.componentUtil); diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyPseudoComponentManager.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyPseudoComponentManager.java index e2c8af440f4..70c7c527529 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyPseudoComponentManager.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/DummyPseudoComponentManager.java @@ -29,9 +29,10 @@ import io.openems.common.exceptions.OpenemsError; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.exceptions.OpenemsException; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; -import io.openems.common.jsonrpc.request.DeleteComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.CreateComponentConfig; +import io.openems.common.jsonrpc.type.DeleteComponentConfig; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.types.EdgeConfig; import io.openems.common.types.EdgeConfig.ActualEdgeConfig; import io.openems.common.types.EdgeConfig.Component; @@ -155,13 +156,13 @@ public EdgeConfig getEdgeConfig() { @Override public void handleCreateComponentConfigRequest(// final User user, // - final CreateComponentConfigRequest request // + final CreateComponentConfig.Request request // ) throws OpenemsNamedException { final var component = componentOf(// request.getComponentId(), // - request.getFactoryPid(), // - request.getProperties() // + request.factoryPid(), // + request.properties() // ); this.components.add(component); @@ -170,17 +171,17 @@ public void handleCreateComponentConfigRequest(// @Override public void handleUpdateComponentConfigRequest(// final User user, // - final UpdateComponentConfigRequest request // + final UpdateComponentConfig.Request request // ) throws OpenemsNamedException { - final var foundComponent = this.getPossiblyDisabledComponent(request.getComponentId()); + final var foundComponent = this.getPossiblyDisabledComponent(request.componentId()); if (foundComponent instanceof DummyOpenemsComponent) { final var component = componentOf(// - request.getComponentId(), // + request.componentId(), // foundComponent.serviceFactoryPid(), // - request.getProperties() // + request.properties() // ); - this.components.removeIf(t -> t.id().equals(request.getComponentId())); + this.components.removeIf(t -> t.id().equals(request.componentId())); this.components.add(component); } if (this.configurationAdmin == null) { @@ -192,11 +193,11 @@ public void handleUpdateComponentConfigRequest(// if (props == null) { continue; } - if (props.get("id") == null || !props.get("id").equals(request.getComponentId())) { + if (props.get("id") == null || !props.get("id").equals(request.componentId())) { continue; } var properties = new Hashtable(); - for (var property : request.getProperties()) { + for (var property : request.properties()) { properties.put(property.getName(), property.getValue()); } configuration.update(properties); @@ -209,9 +210,9 @@ public void handleUpdateComponentConfigRequest(// @Override public void handleDeleteComponentConfigRequest(// final User user, // - final DeleteComponentConfigRequest request // + final DeleteComponentConfig.Request request // ) throws OpenemsNamedException { - this.components.removeIf(t -> t.id().equals(request.getComponentId())); + this.components.removeIf(t -> t.id().equals(request.componentId())); } /** diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/UpdateComponentDirectlyTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/UpdateComponentDirectlyTest.java index dc0f9136cd0..8a1c0fc4f9e 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/UpdateComponentDirectlyTest.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/UpdateComponentDirectlyTest.java @@ -10,8 +10,8 @@ import com.google.common.collect.ImmutableList; import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; -import io.openems.common.jsonrpc.request.CreateComponentConfigRequest; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.CreateComponentConfig; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.core.appmanager.AppManagerTestBundle.PseudoComponentManagerFactory; @@ -34,7 +34,7 @@ public void testAdminOnlyAsInstaller() throws OpenemsNamedException { final var testTest = List.of(new UpdateComponentConfigRequest.Property("id", "evcs1"), new UpdateComponentConfigRequest.Property("debugMode", false)); this.appManagerTestBundle.componentManger.handleCreateComponentConfigRequest(DUMMY_ADMIN, - new CreateComponentConfigRequest("Evcs.Keba.KeContact", testTest)); + new CreateComponentConfig.Request("Evcs.Keba.KeContact", testTest)); this.appManagerTestBundle.sut.handleUpdateAppConfigRequest(DUMMY_ADMIN, new UpdateAppConfig.Request("evcs1", JsonUtils.buildJsonObject().addProperty("debugMode", true)// .build())); diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/dependency/aggregatetask/TestScheduler.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/dependency/aggregatetask/TestScheduler.java index 05708d430bd..a22cd8f9e5f 100644 --- a/io.openems.edge.core/test/io/openems/edge/core/appmanager/dependency/aggregatetask/TestScheduler.java +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/dependency/aggregatetask/TestScheduler.java @@ -14,6 +14,7 @@ import io.openems.common.exceptions.OpenemsError.OpenemsNamedException; import io.openems.common.jsonrpc.request.UpdateComponentConfigRequest; +import io.openems.common.jsonrpc.type.UpdateComponentConfig; import io.openems.common.types.EdgeConfig; import io.openems.common.utils.JsonUtils; import io.openems.edge.common.component.ComponentManager; @@ -83,7 +84,7 @@ public void setSchedulerIds(User user, List ids) public void setSchedulerIds(User user, String... ids) throws OpenemsNamedException { this.componentManager.handleUpdateComponentConfigRequest(user, - new UpdateComponentConfigRequest("scheduler0", List.of(// + new UpdateComponentConfig.Request("scheduler0", List.of(// new UpdateComponentConfigRequest.Property("controllers.ids", Arrays.stream(ids) // .map(JsonPrimitive::new) // .collect(toJsonArray())) // diff --git a/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/relaycount/DeviceHardwareFilterTest.java b/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/relaycount/DeviceHardwareFilterTest.java new file mode 100644 index 00000000000..1a62f880861 --- /dev/null +++ b/io.openems.edge.core/test/io/openems/edge/core/appmanager/validator/relaycount/DeviceHardwareFilterTest.java @@ -0,0 +1,82 @@ +package io.openems.edge.core.appmanager.validator.relaycount; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; + +import org.junit.Before; +import org.junit.Test; + +import com.google.common.collect.ImmutableList; + +import io.openems.edge.app.hardware.IoGpio; +import io.openems.edge.core.appmanager.AppManagerTestBundle; +import io.openems.edge.core.appmanager.Apps; +import io.openems.edge.core.appmanager.OpenemsApp; +import io.openems.edge.io.test.DummyInputOutput; + +public class DeviceHardwareFilterTest { + + private AppManagerTestBundle test; + private DeviceHardwareFilter hardwareFilter; + + private OpenemsApp deviceHardwareApp; + private OpenemsApp deviceHardwareAppWithoutIo; + private OpenemsApp ioApp; + + @Before + public void setUp() throws Exception { + this.test = new AppManagerTestBundle(null, null, t -> { + return ImmutableList.of(// + this.deviceHardwareApp = Apps.techbaseCm3(t), // + this.deviceHardwareAppWithoutIo = Apps.techbaseCm4Max(t), // + this.ioApp = Apps.ioGpio(t) // + ); + }); + + this.hardwareFilter = new DeviceHardwareFilter(this.test.appManagerUtil); + } + + @Test + public void testApplyWithHardwareInstance() throws Exception { + this.test.tryInstallWithMinConfig(this.deviceHardwareApp); + + final var ioInstance = this.test.findFirst(this.ioApp.getAppId()); + final var ioId = ioInstance.properties.get(IoGpio.Property.IO_ID.name()).getAsString(); + + final var relayFilter = this.hardwareFilter.apply(); + assertFalse(relayFilter.componentFilter().test(new DummyInputOutput(ioId))); + assertTrue(relayFilter.componentFilter().test(new DummyInputOutput("someOtherIoId0"))); + } + + @Test + public void testApplyWithoutHardwareInstance() throws Exception { + final var relayFilter = this.hardwareFilter.apply(); + assertTrue(relayFilter.componentFilter().test(new DummyInputOutput("io0"))); + } + + @Test + public void testApplyWithoutIoDependecy() throws Exception { + this.test.tryInstallWithMinConfig(this.deviceHardwareAppWithoutIo); + assertNull(this.test.findFirst(this.ioApp.getAppId())); + + this.test.tryInstallWithMinConfig(this.ioApp); + final var ioInstance = this.test.findFirst(this.ioApp.getAppId()); + final var ioId = ioInstance.properties.get(IoGpio.Property.IO_ID.name()).getAsString(); + + final var relayFilter = this.hardwareFilter.apply(); + assertTrue(relayFilter.componentFilter().test(new DummyInputOutput("io0"))); + assertTrue(relayFilter.componentFilter().test(new DummyInputOutput(ioId))); + } + + @Test + public void testApplyWithOtherIo() throws Exception { + this.test.tryInstallWithMinConfig(this.ioApp); + final var ioInstance = this.test.findFirst(this.ioApp.getAppId()); + final var ioId = ioInstance.properties.get(IoGpio.Property.IO_ID.name()).getAsString(); + + final var relayFilter = this.hardwareFilter.apply(); + assertTrue(relayFilter.componentFilter().test(new DummyInputOutput(ioId))); + } + +} diff --git a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java index 77fda46f75b..16b482551d6 100644 --- a/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java +++ b/io.openems.edge.energy/src/io/openems/edge/energy/v1/optimizer/OptimizerV1.java @@ -2,6 +2,7 @@ import static com.google.common.collect.ImmutableSortedMap.toImmutableSortedMap; import static io.openems.edge.energy.optimizer.Utils.calculateExecutionLimitSeconds; +import static io.openems.edge.energy.optimizer.Utils.calculateSleepMillis; import static io.openems.edge.energy.optimizer.Utils.initializeRandomRegistryForProduction; import static io.openems.edge.energy.v1.optimizer.SimulatorV1.simulate; import static io.openems.edge.energy.v1.optimizer.UtilsV1.createSimulatorParams; @@ -9,8 +10,6 @@ import static io.openems.edge.energy.v1.optimizer.UtilsV1.updateSchedule; import static java.lang.Thread.sleep; -import java.time.Duration; -import java.time.Instant; import java.time.ZonedDateTime; import java.util.Map.Entry; import java.util.TreeMap; @@ -62,15 +61,11 @@ public void forever() throws InterruptedException, OpenemsException { this.createParams(); // this possibly takes forever final var globalContext = this.globalContext.get(); - final var start = Instant.now(globalContext.clock()); - - long executionLimitSeconds; - - // Calculate max execution time till next quarter (with buffer) - executionLimitSeconds = calculateExecutionLimitSeconds(globalContext.clock()); // Find best Schedule - var schedule = SimulatorV1.getBestSchedule(this.params, executionLimitSeconds); + var schedule = SimulatorV1.getBestSchedule(this.params, + // Calculate max execution time till next quarter (with buffer) + calculateExecutionLimitSeconds(globalContext.clock())); // Re-Simulate and keep best Schedule var newSchedule = simulate(this.params, schedule); @@ -93,12 +88,9 @@ public void forever() throws InterruptedException, OpenemsException { // Sleep remaining time if (!(globalContext.clock() instanceof TimeLeapClock)) { - var remainingExecutionLimit = Duration - .between(Instant.now(globalContext.clock()), start.plusSeconds(executionLimitSeconds)).getSeconds(); - if (remainingExecutionLimit > 0) { - this.traceLog(() -> "Sleep [" + remainingExecutionLimit + "s] till next run of Optimizer"); - sleep(remainingExecutionLimit * 1000); - } + var millisTillNextQuarter = calculateSleepMillis(globalContext.clock()); + this.traceLog(() -> "Sleep [" + millisTillNextQuarter + "ms] till next run of Optimizer"); + sleep(millisTillNextQuarter); } } diff --git a/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java b/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java index 5bd53f33e6b..584dae2fdf0 100644 --- a/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java +++ b/io.openems.edge.evcs.alpitronic.hypercharger/src/io/openems/edge/evcs/hypercharger/EvcsAlpitronicHyperchargerImpl.java @@ -47,6 +47,7 @@ import io.openems.edge.evcs.api.Evcs; import io.openems.edge.evcs.api.EvcsPower; import io.openems.edge.evcs.api.ManagedEvcs; +import io.openems.edge.evcs.api.PhaseRotation; import io.openems.edge.evcs.api.Status; import io.openems.edge.evcs.api.WriteHandler; import io.openems.edge.meter.api.ElectricityMeter; @@ -165,6 +166,12 @@ public MeterType getMeterType() { return MeterType.MANAGED_CONSUMPTION_METERED; } + @Override + public PhaseRotation getPhaseRotation() { + // TODO implement handling for rotated Phases + return PhaseRotation.L1_L2_L3; + } + @Override public EvcsPower getEvcsPower() { return this.evcsPower; diff --git a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java index 3902b41ced3..4c4dbab310e 100644 --- a/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java +++ b/io.openems.edge.evcs.api/src/io/openems/edge/evcs/api/Evcs.java @@ -66,7 +66,7 @@ public enum ChannelId implements io.openems.edge.common.channel.ChannelId { * This value is derived from the charging station or calculated during the * charging. When this value is set, the minimum and maximum limits are set at * the same time if the EVCS is a {@link ManagedEvcs}. - * + * *