ReportNode.java

/**
 * Copyright (c) 2021, 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 com.fasterxml.jackson.core.JsonGenerator;

import java.io.IOException;
import java.io.Writer;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.*;

/**
 * A <code>ReportNode</code> allows building up functional reports with a tree hierarchy.
 *
 * <p>Each <code>ReportNode</code> is defined by
 * <ul>
 *     <li>a map of {@link TypedValue} indexed by their keys,</li>
 *     <li>a default {@link String} message template containing the functional message in the default language, which
 *     may contain references to those values or to the values of one of its ancestors,</li>
 *     <li>a {@link String} message key identifying the <strong>unique</strong> corresponding functional report template;
 *     this key is used in particular to build a dictionary of all the message templates,</li>
 *     <li>a collection of <code>ReportNode</code> children, possibly empty.</li>
 * </ul>
 *
 * <p>When the collection of children of a <code>ReportNode</code> is non-empty, the message of the corresponding
 * <code>ReportNode</code> is expected to summarize the children content. In order to help this summary, the
 * {@link TypedValue} can be provided after the <code>ReportNode</code> creation with {@link #addTypedValue}/
 * {@link #addUntypedValue} methods.
 * Note that the summarizing template should be succinct: 120 characters is a good limit for the message string length
 * (once formatted).
 *
 * <p>The {@link TypedValue} values should have a meaningful type to possibly enrich the message content. Please reuse
 * the generic types provided in {@link TypedValue} when possible.
 *
 * <p>The values {@link TypedValue} values should be referred to by their key in the message template, using the <code>${key}</code>
 * syntax, in order to be later replaced by {@link org.apache.commons.text.StringSubstitutor} for instance when formatting
 * the string for the end user.
 * The <code>ReportNode</code> values may be referred to within the corresponding messageTemplate, or within any of its
 * descendants, even if the value is added afterward.
 * Be aware that any descendant might override a value by giving a new value to an existing key.
 * All implementations of <code>ReportNode</code> need to take that inheritance into account.
 *
 * <p>Instances of <code>ReportNode</code> are not thread-safe.
 * When a new thread is created, a new <code>ReportNode</code> should be provided to the process in that thread.
 * A <code>ReportNode</code> is not meant to be shared with other threads.
 * Therefore, it should rather not be saved as a class parameter of an object which could be used by separate threads.
 * In those cases it should instead be passed on in methods through their arguments.
 *
 * <p>The <code>ReportNode</code> is designed for multilingual support.
 * Indeed, each <code>ReportNode</code> message can be translated based on their key and using the value keys in the desired order.
 *
 * @author Florian Dupuy {@literal <florian.dupuy at rte-france.com>}
 */
public interface ReportNode {

    /**
     * A No-op implementation of <code>ReportNode</code>
     */
    ReportNode NO_OP = new ReportNodeNoOp();

    /**
     * Create a new builder for creating a root <code>ReportNode</code>.
     * @return a {@link ReportNodeBuilder}
     */
    static ReportNodeBuilder newRootReportNode() {
        return new ReportNodeRootBuilderImpl();
    }

    /**
     * Get the message key for current node.
     * Note that each key needs to correspond to a unique message template.
     * This allows to build up a dictionary of message templates indexed by their key.
     * @return the key
     */
    String getMessageKey();

    /**
     * Get the message template for current node.
     * @return the message template
     */
    String getMessageTemplate();

    /**
     * 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.
     * @return the message
     */
    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)
     * @return the values unmodifiable map
     */
    Map<String, TypedValue> getValues();

    /**
     * Get the value corresponding to the given key
     *
     * @param key the key to request
     * @return the value
     */
    Optional<TypedValue> getValue(String key);

    /**
     * Get the children of current node
     *
     * @return the children nodes
     */
    List<ReportNode> getChildren();

    /**
     * Create a new adder to create a <code>ReportNode</code> child.
     *
     * @return the created <code>ReportNodeAdder</code>
     */
    ReportNodeAdder newReportNode();

    /**
     * Get the {@link TreeContext} of the corresponding {@link ReportNode} tree.
     */
    TreeContext getTreeContext();

    /**
     * Add a <code>ReportNode</code> as a child of current <code>ReportNode</code>.
     *
     * @param reportRoot the <code>ReportNode</code> to add, it needs to be a root <code>ReportNode</code>
     */
    void include(ReportNode reportRoot);

    /**
     * Copy the given <code>ReportNode</code> and inserts the resulting <code>ReportNode</code> as a child of current <code>ReportNode</code>.
     *
     * @param reportNode the <code>ReportNode</code> to copy into the children of current <code>ReportNode</code>
     */
    void addCopy(ReportNode reportNode);

    /**
     * Serialize the current report node
     * @param generator the jsonGenerator to use for serialization
     */
    void writeJson(JsonGenerator generator) throws IOException;

    /** Add one typed String value */
    ReportNode addTypedValue(String key, String value, String type);

    /** Add one untyped String value */
    ReportNode addUntypedValue(String key, String value);

    /** Add one typed double value */
    ReportNode addTypedValue(String key, double value, String type);

    /** Add one untyped double value */
    ReportNode addUntypedValue(String key, double value);

    /** Add one typed float value */
    ReportNode addTypedValue(String key, float value, String type);

    /** Add one untyped float value */
    ReportNode addUntypedValue(String key, float value);

    /** Add one typed int value */
    ReportNode addTypedValue(String key, int value, String type);

    /** Add one untyped int value */
    ReportNode addUntypedValue(String key, int value);

    /** Add one typed long value */
    ReportNode addTypedValue(String key, long value, String type);

    /** Add one untyped long value */
    ReportNode addUntypedValue(String key, long value);

    /** Add one typed boolean value */
    ReportNode addTypedValue(String key, boolean value, String type);

    /** Add one untyped boolean value */
    ReportNode addUntypedValue(String key, boolean value);

    /** Add the {@link TypedValue#SEVERITY} typed value associated to {@link ReportConstants#SEVERITY_KEY} key */
    ReportNode addSeverity(TypedValue severity);

    /** Add the {@link String} value for the {@link TypedValue#SEVERITY} type associated to {@link ReportConstants#SEVERITY_KEY} key */
    ReportNode addSeverity(String severity);

    /**
     * Print to given path the current report node and its descendants
     * @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, formatter);
        }
    }

    /**
     * Print to given writer the current report node and its descendants
     * @param writer the writer to write to
     */
    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;
}