diff --git a/commons/src/main/java/com/powsybl/commons/report/ReportFormatter.java b/commons/src/main/java/com/powsybl/commons/report/ReportFormatter.java new file mode 100644 index 00000000000..8a5669e7479 --- /dev/null +++ b/commons/src/main/java/com/powsybl/commons/report/ReportFormatter.java @@ -0,0 +1,18 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.commons.report; + +/** + * @author Florian Dupuy {@literal } + */ +@FunctionalInterface +public interface ReportFormatter { + ReportFormatter DEFAULT = t -> t.getValue().toString(); + + String format(TypedValue value); +} diff --git a/commons/src/main/java/com/powsybl/commons/report/ReportNode.java b/commons/src/main/java/com/powsybl/commons/report/ReportNode.java index a882845d375..58b0ae2fc14 100644 --- a/commons/src/main/java/com/powsybl/commons/report/ReportNode.java +++ b/commons/src/main/java/com/powsybl/commons/report/ReportNode.java @@ -88,7 +88,17 @@ static ReportNodeBuilder newRootReportNode() { * corresponding values, either contained in current node or in one of its parents. * @return the message */ - String getMessage(); + default String getMessage() { + return getMessage(ReportFormatter.DEFAULT); + } + + /** + * Get the message of current node, replacing ${key} references in the message template with the + * corresponding values, either contained in current node or in one of its parents. + * @param formatter the formatter to use to transform any value into a string + * @return the message + */ + String getMessage(ReportFormatter formatter); /** * Get the values which belong to current node (does not include the inherited values) @@ -138,11 +148,20 @@ static ReportNodeBuilder newRootReportNode() { /** * Print to given path the current report node and its descendants - * @param path the writer to write to + * @param path the path to write to */ default void print(Path path) throws IOException { + print(path, ReportFormatter.DEFAULT); + } + + /** + * Print to given path the current report node and its descendants + * @param path the path to write to + * @param formatter the formatter to use to print values + */ + default void print(Path path, ReportFormatter formatter) throws IOException { try (Writer writer = Files.newBufferedWriter(path, StandardCharsets.UTF_8)) { - print(writer); + print(writer, formatter); } } @@ -150,5 +169,14 @@ default void print(Path path) throws IOException { * Print to given writer the current report node and its descendants * @param writer the writer to write to */ - void print(Writer writer) throws IOException; + default void print(Writer writer) throws IOException { + print(writer, ReportFormatter.DEFAULT); + } + + /** + * Print to given writer the current report node and its descendants + * @param writer the writer to write to + * @param formatter the formatter to use to print values + */ + void print(Writer writer, ReportFormatter formatter) throws IOException; } diff --git a/commons/src/main/java/com/powsybl/commons/report/ReportNodeImpl.java b/commons/src/main/java/com/powsybl/commons/report/ReportNodeImpl.java index 2ce015f9248..e7a89de231e 100644 --- a/commons/src/main/java/com/powsybl/commons/report/ReportNodeImpl.java +++ b/commons/src/main/java/com/powsybl/commons/report/ReportNodeImpl.java @@ -109,14 +109,14 @@ public Map getValues() { } @Override - public String getMessage() { + public String getMessage(ReportFormatter formatter) { return Optional.ofNullable(getTreeContext().getDictionary().get(messageKey)) - .map(messageTemplate -> new StringSubstitutor(vk -> getValueAsString(vk).orElse(null)).replace(messageTemplate)) + .map(messageTemplate -> new StringSubstitutor(vk -> getValueAsString(vk, formatter).orElse(null)).replace(messageTemplate)) .orElse("(missing message key in dictionary)"); } - public Optional getValueAsString(String valueKey) { - return getValue(valueKey).map(TypedValue::getValue).map(Object::toString); + public Optional getValueAsString(String valueKey, ReportFormatter formatter) { + return getValue(valueKey).map(formatter::format); } @Override @@ -183,24 +183,24 @@ public List getChildren() { } @Override - public void print(Writer writer) throws IOException { - print(writer, ""); + public void print(Writer writer, ReportFormatter formatter) throws IOException { + print(writer, "", formatter); } - private void print(Writer writer, String indentationStart) throws IOException { + private void print(Writer writer, String indentationStart, ReportFormatter formatter) throws IOException { if (children.isEmpty()) { - print(writer, indentationStart, ""); + print(writer, indentationStart, "", formatter); } else { - print(writer, indentationStart, "+ "); + print(writer, indentationStart, "+ ", formatter); String childrenIndent = indentationStart + " "; for (ReportNodeImpl child : children) { - child.print(writer, childrenIndent); + child.print(writer, childrenIndent, formatter); } } } - private void print(Writer writer, String indent, String prefix) throws IOException { - writer.append(indent).append(prefix).append(getMessage()).append(System.lineSeparator()); + private void print(Writer writer, String indent, String prefix, ReportFormatter formatter) throws IOException { + writer.append(indent).append(prefix).append(getMessage(formatter)).append(System.lineSeparator()); } public static ReportNodeImpl parseJsonNode(JsonParser parser, ObjectMapper objectMapper, TreeContextImpl treeContext, ReportNodeVersion version) throws IOException { diff --git a/commons/src/main/java/com/powsybl/commons/report/ReportNodeNoOp.java b/commons/src/main/java/com/powsybl/commons/report/ReportNodeNoOp.java index c610f3e7e34..d2c26b580e1 100644 --- a/commons/src/main/java/com/powsybl/commons/report/ReportNodeNoOp.java +++ b/commons/src/main/java/com/powsybl/commons/report/ReportNodeNoOp.java @@ -45,7 +45,7 @@ public String getMessageTemplate() { } @Override - public String getMessage() { + public String getMessage(ReportFormatter formatter) { return null; } @@ -70,7 +70,7 @@ public void writeJson(JsonGenerator generator) throws IOException { } @Override - public void print(Writer writer) throws IOException { + public void print(Writer writer, ReportFormatter formatter) throws IOException { // No-op } diff --git a/commons/src/test/java/com/powsybl/commons/report/ReportFormatterTest.java b/commons/src/test/java/com/powsybl/commons/report/ReportFormatterTest.java new file mode 100644 index 00000000000..68066dd26d4 --- /dev/null +++ b/commons/src/test/java/com/powsybl/commons/report/ReportFormatterTest.java @@ -0,0 +1,57 @@ +/** + * Copyright (c) 2025, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + * SPDX-License-Identifier: MPL-2.0 + */ +package com.powsybl.commons.report; + +import org.junit.jupiter.api.Test; + +import java.util.Locale; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +/** + * @author Florian Dupuy {@literal } + */ +class ReportFormatterTest { + + @Test + void test() { + ReportNode root = ReportNode.newRootReportNode() + .withMessageTemplate("formatTest", """ + Formatter test message + double default format: ${doubleDefaultFormat} + double format based on type: ${doubleSpecificFormat} + float format based on type: ${floatSpecificFormat} + string default format: ${stringDefaultFormat} + string format based on type: ${stringSpecificFormat}""") + .withUntypedValue("doubleDefaultFormat", 4.35684975) + .withTypedValue("doubleSpecificFormat", 4.4664798548, TypedValue.ACTIVE_POWER) + .withTypedValue("floatSpecificFormat", 0.6f, TypedValue.IMPEDANCE) + .withUntypedValue("stringDefaultFormat", "tiny") + .withTypedValue("stringSpecificFormat", "This is a sentence which needs to be truncated", "LONG_SENTENCE") + .build(); + ReportFormatter customFormatter = typedValue -> { + if (typedValue.getType().equals(TypedValue.ACTIVE_POWER) && typedValue.getValue() instanceof Double d) { + return String.format(Locale.CANADA_FRENCH, "%2.4f", d); + } + if (typedValue.getType().equals(TypedValue.IMPEDANCE) && typedValue.getValue() instanceof Float f) { + return String.format(Locale.CANADA_FRENCH, "%.2f", f); + } + if (typedValue.getType().equals("LONG_SENTENCE") && typedValue.getValue() instanceof String s) { + return s.substring(0, 18); + } + return typedValue.getValue().toString(); + }; + assertEquals(""" + Formatter test message + double default format: 4.35684975 + double format based on type: 4,4665 + float format based on type: 0,60 + string default format: tiny + string format based on type: This is a sentence""", root.getMessage(customFormatter)); + } +} diff --git a/docs/user/functional_logs/export.md b/docs/user/functional_logs/export.md index 86190ed498b..669c1f75000 100644 --- a/docs/user/functional_logs/export.md +++ b/docs/user/functional_logs/export.md @@ -47,17 +47,26 @@ An example of the current version 2.1 of the serialization is below: ``` ## Display format -In order to have an overview of a report node, two print methods are provided in the API: -- for printing to a `Path` +To get an overview of a report node, several print methods are provided in the API, with the possibility to provide your own `Formatter` - +`Formatter` is a functional interface that specifies how to get a `String` from a `TypedValue`. +- To print to a `Path`: ```java reportNode.print(path); +reportNode.print(path, formatter); ``` -- for printing to a `Writer` +- To print to a `Writer`: ```java reportNode.print(writer); +reportNode.print(writer, formatter); ``` -The correspond multiline string of above example is below. +In both cases, giving a custom formatter allows to do specific formatting based on types for instance. +If no formatter is provided, the default one is used: +```java +typedValue -> typedValue.getValue().toString() +``` + +The corresponding multiline string of above example is below. The `+` character and the indentation are used to show the tree hierarchy. ```text + template with typed value