Skip to content
Snippets Groups Projects
ConfigTree.cpp 13.5 KiB
Newer Older
  • Learn to ignore specific revisions
  •  * \copyright
    
     * Copyright (c) 2012-2023, 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(ConfigTree::PTree&& top_level_tree,
    
                           std::string filename,
                           Callback error_cb,
                           Callback warning_cb)
    
        : top_level_tree_(std::make_shared<PTree>(std::move(top_level_tree))),
          tree_(top_level_tree_.get()),
    
          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)
    
        : top_level_tree_(parent.top_level_tree_),
          tree_(&tree),
    
          path_(joinPaths(parent.path_, root)),
    
          filename_(parent.filename_),
    
          onerror_(parent.onerror_),
          onwarning_(parent.onwarning_)
    
    {
        checkKeyname(root);
    }
    
    
    ConfigTree::ConfigTree(ConfigTree&& other)
    
        : top_level_tree_(std::move(other.top_level_tree_)),
          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();
    
        top_level_tree_ = std::move(other.top_level_tree_);
    
        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