Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Formatter for printing ReportNode #3317

Merged
merged 5 commits into from
Feb 10, 2025
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 18 additions & 0 deletions commons/src/main/java/com/powsybl/commons/report/Formatter.java
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 Formatter {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we can name it ReportFormatter or MessageFormatter to avoid confusion with technical / JDK formatting classes ?

Formatter 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(Formatter.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(Formatter 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, Formatter.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, Formatter 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, Formatter.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, Formatter 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(Formatter 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, Formatter 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, Formatter formatter) throws IOException {
print(writer, "", formatter);
}

private void print(Writer writer, String indentationStart) throws IOException {
private void print(Writer writer, String indentationStart, Formatter 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, Formatter 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(Formatter 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, Formatter 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 FormatterTest {

@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();
Formatter 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