Skip to content
Snippets Groups Projects
ConfigTree.cpp 13.2 KiB
Newer Older
 * \copyright
 * Copyright (c) 2012-2021, OpenGeoSys Community (http://www.opengeosys.org)
 *            Distributed under a Modified BSD License.
 *              See accompanying file LICENSE.txt or
 *              http://www.opengeosys.org/project/license
 *
 */

#include "ConfigTree.h"
#include <forward_list>
#include <utility>
Dmitri Naumov's avatar
Dmitri Naumov committed
#include "Logging.h"
// Explicitly instantiate the boost::property_tree::ptree which is a typedef to
// the following basic_ptree.
template class boost::property_tree::basic_ptree<std::string, std::string,
//! Collects swallowed error messages raised by the check during destruction of
//! ConfigTree instances.
static std::forward_list<std::string> configtree_destructor_error_messages;

namespace BaseLib
{
const char ConfigTree::pathseparator = '/';
const std::string ConfigTree::key_chars_start = "abcdefghijklmnopqrstuvwxyz";
const std::string ConfigTree::key_chars = key_chars_start + "_0123456789";
ConfigTree::ConfigTree(PTree const& tree,
                       std::string filename,
                       Callback error_cb,
                       Callback warning_cb)
    : tree_(&tree),
      filename_(std::move(filename)),
      onerror_(std::move(error_cb)),
      onwarning_(std::move(warning_cb))
        OGS_FATAL("ConfigTree: No valid error handler provided.");
    if (!onwarning_)
    {
        OGS_FATAL("ConfigTree: No valid warning handler provided.");
ConfigTree::ConfigTree(PTree const& tree, ConfigTree const& parent,
                       std::string const& root)
    : tree_(&tree),
      path_(joinPaths(parent.path_, root)),
      filename_(parent.filename_),
      onerror_(parent.onerror_),
      onwarning_(parent.onwarning_)
{
    checkKeyname(root);
}

ConfigTree::ConfigTree(ConfigTree&& other)
    : tree_(other.tree_),
      path_(std::move(other.path_)),
      filename_(std::move(other.filename_)),
      visited_params_(std::move(other.visited_params_)),
      have_read_data_(other.have_read_data_),
      onerror_(std::move(other.onerror_)),
      onwarning_(std::move(other.onwarning_))
{
    other.tree_ = nullptr;
ConfigTree::~ConfigTree()
    if (std::uncaught_exceptions() > 0)
    {
        /* If the stack unwinds the check below shall be suppressed in order to
         * not accumulate false-positive configuration errors.
         */
        return;
    }

Dmitri Naumov's avatar
Dmitri Naumov committed
    try
    {
        checkAndInvalidate();
Dmitri Naumov's avatar
Dmitri Naumov committed
    }
    catch (std::exception& e)
    {
Dmitri Naumov's avatar
Dmitri Naumov committed
        ERR("{:s}", e.what());
        configtree_destructor_error_messages.push_front(e.what());
    }
Dmitri Naumov's avatar
Dmitri Naumov committed
ConfigTree& ConfigTree::operator=(ConfigTree&& other)
    checkAndInvalidate();
    tree_ = other.tree_;
    other.tree_ = nullptr;
    path_ = std::move(other.path_);
    filename_ = std::move(other.filename_);
    visited_params_ = std::move(other.visited_params_);
    have_read_data_ = other.have_read_data_;
    onerror_ = std::move(other.onerror_);
    onwarning_ = std::move(other.onwarning_);
    return *this;
Dmitri Naumov's avatar
Dmitri Naumov committed
ConfigTree ConfigTree::getConfigParameter(std::string const& root) const
{
    auto ct = getConfigSubtree(root);
    if (ct.hasChildren())
        error("Requested parameter <" + root + "> actually is a subtree.");
std::optional<ConfigTree> ConfigTree::getConfigParameterOptional(
    std::string const& root) const
{
    auto ct = getConfigSubtreeOptional(root);
    if (ct && ct->hasChildren())
        error("Requested parameter <" + root + "> actually is a subtree.");
Dmitri Naumov's avatar
Dmitri Naumov committed
Range<ConfigTree::ParameterIterator> ConfigTree::getConfigParameterList(
    const std::string& param) const
    markVisited(param, Attr::TAG, true);
    auto p = tree_->equal_range(param);
Dmitri Naumov's avatar
Dmitri Naumov committed
    return Range<ParameterIterator>(ParameterIterator(p.first, param, *this),
                                    ParameterIterator(p.second, param, *this));
Dmitri Naumov's avatar
Dmitri Naumov committed
ConfigTree ConfigTree::getConfigSubtree(std::string const& root) const
Dmitri Naumov's avatar
Dmitri Naumov committed
    if (auto t = getConfigSubtreeOptional(root))
    {
        return std::move(*t);
    }
    error("Key <" + root + "> has not been found.");
std::optional<ConfigTree> ConfigTree::getConfigSubtreeOptional(
    std::string const& root) const
{
    checkUnique(root);

    if (auto subtree = tree_->get_child_optional(root))
    {
        markVisited(root, Attr::TAG, false);
        return ConfigTree(*subtree, *this, root);
    markVisited(root, Attr::TAG, true);
    return std::nullopt;
Dmitri Naumov's avatar
Dmitri Naumov committed
Range<ConfigTree::SubtreeIterator> ConfigTree::getConfigSubtreeList(
    std::string const& root) const
{
    checkUnique(root);
    markVisited(root, Attr::TAG, true);
    auto p = tree_->equal_range(root);
Dmitri Naumov's avatar
Dmitri Naumov committed
    return Range<SubtreeIterator>(SubtreeIterator(p.first, root, *this),
                                  SubtreeIterator(p.second, root, *this));
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::ignoreConfigParameter(const std::string& param) const
{
    checkUnique(param);
    // if not found, peek only
    bool peek_only = tree_->find(param) == tree_->not_found();
    markVisited(param, Attr::TAG, peek_only);
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::ignoreConfigAttribute(const std::string& attr) const
{
    checkUniqueAttr(attr);

    // Exercise: Guess what not! (hint: if not found, peek only)
    // Btw. (not a hint) tree_->find() does not seem to work here.
    bool peek_only = !tree_->get_child_optional("<xmlattr>." + attr);
    markVisited(attr, Attr::ATTR, peek_only);
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::ignoreConfigParameterAll(const std::string& param) const
{
    checkUnique(param);
    auto& ct = markVisited(param, Attr::TAG, true);
    auto p = tree_->equal_range(param);
Dmitri Naumov's avatar
Dmitri Naumov committed
    for (auto it = p.first; it != p.second; ++it)
    {
void ConfigTree::error(const std::string& message) const
    onerror_(filename_, path_, message);
    OGS_FATAL(
        "ConfigTree: The error handler does not break out of the normal "
        "control flow.");
void ConfigTree::warning(const std::string& message) const
    onwarning_(filename_, path_, message);
void ConfigTree::onerror(const std::string& filename, const std::string& path,
Dmitri Naumov's avatar
Dmitri Naumov committed
                         const std::string& message)
    OGS_FATAL("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
              message);
void ConfigTree::onwarning(const std::string& filename, const std::string& path,
Dmitri Naumov's avatar
Dmitri Naumov committed
                           const std::string& message)
    WARN("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
         message);
void ConfigTree::assertNoSwallowedErrors()
{
    if (configtree_destructor_error_messages.empty())

    ERR("ConfigTree: There have been errors when parsing the configuration "
        "file(s):");

Dmitri Naumov's avatar
Dmitri Naumov committed
    for (auto const& msg : configtree_destructor_error_messages)
    {
    }

    OGS_FATAL("There have been errors when parsing the configuration file(s).");
}

Dmitri Naumov's avatar
Dmitri Naumov committed
std::string ConfigTree::shortString(const std::string& s)
{
    const std::size_t maxlen = 100;

    if (s.size() < maxlen)
    {
        return s;
    }
Dmitri Naumov's avatar
Dmitri Naumov committed
    return s.substr(0, maxlen - 3) + "...";
void ConfigTree::checkKeyname(std::string const& key) const
Dmitri Naumov's avatar
Dmitri Naumov committed
    if (key.empty())
    {
        error("Search for empty key.");
Dmitri Naumov's avatar
Dmitri Naumov committed
    }
    else if (key_chars_start.find(key.front()) == std::string::npos)
    {
        error("Key <" + key + "> starts with an illegal character.");
Dmitri Naumov's avatar
Dmitri Naumov committed
    }
    else if (key.find_first_not_of(key_chars, 1) != std::string::npos)
    {
        error("Key <" + key + "> contains illegal characters.");
Dmitri Naumov's avatar
Dmitri Naumov committed
    }
    else if (key.find("__") != std::string::npos)
    {
        // This is illegal because we use parameter names to generate doxygen
        // page names. Thereby "__" acts as a separator character. Choosing
        // other separators is not possible because of observed limitations
        // for valid doxygen page names.
        error("Key <" + key + "> contains double underscore.");
Dmitri Naumov's avatar
Dmitri Naumov committed
std::string ConfigTree::joinPaths(const std::string& p1,
                                  const std::string& p2) const
Dmitri Naumov's avatar
Dmitri Naumov committed
    if (p2.empty())
    {
        error("Second path to be joined is empty.");
    }
    if (p1.empty())
    {
        return p2;
    }
    return p1 + pathseparator + p2;
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::checkUnique(const std::string& key) const
    checkKeyname(key);
    if (visited_params_.find({Attr::TAG, key}) != visited_params_.end())
    {
        error("Key <" + key + "> has already been processed.");
    }
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::checkUniqueAttr(const std::string& attr) const
Dmitri Naumov's avatar
Dmitri Naumov committed
    // Workaround for handling attributes with xml namespaces and uppercase
    // letters.
    if (attr.find(':') != std::string::npos)
    {
        auto pos = decltype(std::string::npos){0};

        // Replace colon and uppercase letters with an allowed character 'a'.
        // That means, attributes containing a colon are also allowed to contain
        // uppercase letters.
        auto attr2 = attr;
            pos = attr2.find_first_of(":ABCDEFGHIJKLMNOPQRSTUVWXYZ", pos);
            if (pos != std::string::npos)
        } while (pos != std::string::npos);
    if (visited_params_.find({Attr::ATTR, attr}) != visited_params_.end())
    {
        error("Attribute '" + attr + "' has already been processed.");
Dmitri Naumov's avatar
Dmitri Naumov committed
ConfigTree::CountType& ConfigTree::markVisited(std::string const& key,
                                               Attr const is_attr,
                                               bool const peek_only) const
    return markVisited<ConfigTree>(key, is_attr, peek_only);
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::markVisitedDecrement(Attr const is_attr,
                                      std::string const& key) const
{
    auto const type = std::type_index(typeid(nullptr));

    auto p = visited_params_.emplace(std::make_pair(is_attr, key),
                                     CountType{-1, type});
Dmitri Naumov's avatar
Dmitri Naumov committed
    if (!p.second)
    {  // no insertion happened
        auto& v = p.first->second;
        --v.count;
    }
}

Dmitri Naumov's avatar
Dmitri Naumov committed
bool ConfigTree::hasChildren() const
    auto const& tree = *tree_;
    if (tree.begin() == tree.end())
    {
        return false;  // no children
    }
    if (tree.front().first == "<xmlattr>" && (++tree.begin()) == tree.end())
    {
        return false;  // only attributes
    }
Dmitri Naumov's avatar
Dmitri Naumov committed
void ConfigTree::checkAndInvalidate()
    // Note: due to a limitation in boost::property_tree it is not possible
    // to discriminate between <tag></tag> and <tag/> in the input file.
    // In both cases data() will be empty.
    if ((!have_read_data_) && !tree_->data().empty())
    {
        warning("The immediate data `" + shortString(tree_->data()) +
                "' of this tag has not been read.");
    }

    // iterate over children
    for (auto const& p : *tree_)
    {
        if (p.first != "<xmlattr>")
        {  // attributes are handled below
            markVisitedDecrement(Attr::TAG, p.first);
    }

    // iterate over attributes
    if (auto attrs = tree_->get_child_optional("<xmlattr>"))
    {
Dmitri Naumov's avatar
Dmitri Naumov committed
        for (auto const& p : *attrs)
        {
            markVisitedDecrement(Attr::ATTR, p.first);
    for (auto const& p : visited_params_)
Dmitri Naumov's avatar
Dmitri Naumov committed
        auto const& tag = p.first.second;
        auto const& count = p.second.count;

Dmitri Naumov's avatar
Dmitri Naumov committed
        switch (p.first.first)
        {
            case Attr::ATTR:
                if (count > 0)
                {
                    warning("XML attribute '" + tag + "' has been read " +
                            std::to_string(count) +
                            " time(s) more than it was present in the "
                            "configuration tree.");
                }
                else if (count < 0)
                {
                    warning("XML attribute '" + tag + "' has been read " +
                            std::to_string(-count) +
                            " time(s) less than it was present in the "
                            "configuration tree.");
                }
                break;
            case Attr::TAG:
                if (count > 0)
                {
                    warning("Key <" + tag + "> has been read " +
                            std::to_string(count) +
                            " time(s) more than it was present in the "
                            "configuration tree.");
                }
                else if (count < 0)
                {
                    warning("Key <" + tag + "> has been read " +
                            std::to_string(-count) +
                            " time(s) less than it was present in the "
                            "configuration tree.");
                }
Dmitri Naumov's avatar
Dmitri Naumov committed
    // The following invalidates this instance, s.t. it can not be read from it
    // anymore, but it also prevents double-checking.
Dmitri Naumov's avatar
Dmitri Naumov committed
void checkAndInvalidate(ConfigTree& conf)
{
    conf.checkAndInvalidate();
}

void checkAndInvalidate(ConfigTree* const conf)
    if (conf)
    {
        conf->checkAndInvalidate();
    }
void checkAndInvalidate(std::unique_ptr<ConfigTree> const& conf)
    if (conf)
    {
        conf->checkAndInvalidate();
    }
Dmitri Naumov's avatar
Dmitri Naumov committed
}  // namespace BaseLib