Skip to content

Commit

Permalink
ReportFormatter for printing ReportNode (#3317)
Browse files Browse the repository at this point in the history
* Add formatter to getMessage and print
* Add unit test
* Add doc

Signed-off-by: Florian Dupuy <[email protected]>
  • Loading branch information
flo-dup authored Feb 10, 2025
1 parent c4d300e commit 3198386
Show file tree
Hide file tree
Showing 6 changed files with 134 additions and 22 deletions.
Original file line number Diff line number Diff line change
@@ -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 <florian.dupuy at rte-france.com>}
*/
@FunctionalInterface
public interface ReportFormatter {
ReportFormatter DEFAULT = t -> t.getValue().toString();

String format(TypedValue value);
}
36 changes: 32 additions & 4 deletions commons/src/main/java/com/powsybl/commons/report/ReportNode.java
Original file line number Diff line number Diff line change
Expand Up @@ -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 <code>${key}</code> 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)
Expand Down Expand Up @@ -138,17 +148,35 @@ 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);
}
}

/**
* 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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -109,14 +109,14 @@ public Map<String, TypedValue> 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<String> getValueAsString(String valueKey) {
return getValue(valueKey).map(TypedValue::getValue).map(Object::toString);
public Optional<String> getValueAsString(String valueKey, ReportFormatter formatter) {
return getValue(valueKey).map(formatter::format);
}

@Override
Expand Down Expand Up @@ -183,24 +183,24 @@ public List<ReportNode> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ public String getMessageTemplate() {
}

@Override
public String getMessage() {
public String getMessage(ReportFormatter formatter) {
return null;
}

Expand All @@ -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
}

Expand Down
Original file line number Diff line number Diff line change
@@ -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 <florian.dupuy at rte-france.com>}
*/
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));
}
}
17 changes: 13 additions & 4 deletions docs/user/functional_logs/export.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down

0 comments on commit 3198386

Please sign in to comment.