diff --git a/Applications/ApplicationsLib/ProjectData.cpp b/Applications/ApplicationsLib/ProjectData.cpp index 947a3eaa141035961bada73d95c649f803fb2a8a..93398f45340adfd1e2977d46f8b52c834ddd8621 100644 --- a/Applications/ApplicationsLib/ProjectData.cpp +++ b/Applications/ApplicationsLib/ProjectData.cpp @@ -28,6 +28,8 @@ #include "FileIO/XmlIO/Boost/BoostXmlGmlInterface.h" #include "FileIO/readMeshFromFile.h" +#include "BaseLib/ConfigTreeNew.h" + namespace detail { static @@ -61,7 +63,9 @@ ProjectData::ProjectData(BaseLib::ConfigTree const& project_config, _mesh_vec.push_back(mesh); // process variables - parseProcessVariables(project_config.get_child("process_variables")); + + BaseLib::ConfigTreeNew var_conf(project_config.get_child("process_variables")); + parseProcessVariables(var_conf); // parameters parseParameters(project_config.get_child("parameters")); @@ -174,7 +178,7 @@ bool ProjectData::isMeshNameUniqueAndProvideUniqueName(std::string &name) const } void ProjectData::parseProcessVariables( - BaseLib::ConfigTree const& process_variables_config) + BaseLib::ConfigTreeNew& process_variables_config) { DBUG("Parse process variables:") if (_geoObjects == nullptr) { @@ -191,12 +195,12 @@ void ProjectData::parseProcessVariables( return; } - _process_variables.reserve(process_variables_config.size()); + // _process_variables.reserve(process_variables_config.size()); - for (auto it : process_variables_config) { - BaseLib::ConfigTree const& var_config = it.second; + for (auto var_config + : process_variables_config.getConfSubtreeList("process_variable")) { // TODO Extend to referenced meshes. - _process_variables.emplace_back(var_config,*_mesh_vec[0],*_geoObjects); + _process_variables.emplace_back(var_config, *_mesh_vec[0], *_geoObjects); } } diff --git a/Applications/ApplicationsLib/ProjectData.h b/Applications/ApplicationsLib/ProjectData.h index d5fa7f0f8604874c80e1d1c1c609237665443b69..a537af688a88bc615a1e58a25abcdedce75bd3d9 100644 --- a/Applications/ApplicationsLib/ProjectData.h +++ b/Applications/ApplicationsLib/ProjectData.h @@ -167,7 +167,7 @@ private: /// Parses the process variables configuration and creates new variables for /// each variable entry passing the corresponding subtree to the process /// variable constructor. - void parseProcessVariables(BaseLib::ConfigTree const& process_variables_config); + void parseProcessVariables(BaseLib::ConfigTreeNew& process_variables_config); /// Parses the parameters configuration and saves them in a list. /// Checks if a parameter has name tag. diff --git a/BaseLib/ConfigTreeNew-impl.h b/BaseLib/ConfigTreeNew-impl.h new file mode 100644 index 0000000000000000000000000000000000000000..8f2ad6521cf27277c10c589f21593c45a1be94b4 --- /dev/null +++ b/BaseLib/ConfigTreeNew-impl.h @@ -0,0 +1,159 @@ +/** + * \copyright + * Copyright (c) 2012-2015, 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 "ConfigTreeNew.h" + +#include <logog/include/logog.hpp> + +namespace BaseLib +{ + +//! Wraps a pair of iterators for use as a range in range-based for-loops. +template<typename Iterator> +class Range +{ +public: + explicit Range(Iterator begin, Iterator end) + : _begin(begin), _end(end) + {} + + Iterator begin() const { return _begin; } + Iterator end() const { return _end; } +private: + Iterator _begin; + Iterator _end; +}; + +template<typename T> +T +ConfigTreeNew:: +getConfParam(std::string const& param) +{ + auto p = getConfParamOptional<T>(param); + if (p) return *p; + + error("Key <" + param + "> has not been found"); + return T(); +} + +template<typename T> +T +ConfigTreeNew:: +getConfParam(std::string const& param, T const& default_value) +{ + auto p = getConfParamOptional<T>(param); + if (p) return *p; + return default_value; +} + +template<typename T> +boost::optional<T> +ConfigTreeNew:: +getConfParamOptional(std::string const& param) +{ + checkUnique(param); + auto p = _tree->get_child_optional(param); + + bool peek_only = p == boost::none; + markVisited<T>(param, peek_only); + + if (p) { + auto v = p->get_value_optional<T>(); + if (v) { + return v; + } else { + error("Value for key <" + param + "> `" + shortString(p->data()) + + "' not convertible to the desired type."); + } + } + + return boost::none; +} + +template<typename T> +Range<ConfigTreeNew::ValueIterator<T> > +ConfigTreeNew:: +getConfParamList(std::string const& param) +{ + checkUnique(param); + markVisited<T>(param, true); + + auto p = _tree->equal_range(param); + return Range<ValueIterator<T> >( + ValueIterator<T>(p.first, param, *this), + ValueIterator<T>(p.second, param, *this)); +} + +template<typename T> +T +ConfigTreeNew:: +peekConfParam(std::string const& param) +{ + checkKeyname(param); + + auto p =_tree->get_child_optional(param); + + if (!p) { + error("Key <" + param + "> has not been found"); + } else { + try { + return p->get_value<T>(); + } catch (boost::property_tree::ptree_bad_data) { + error("Value for key <" + param + "> `" + shortString(p->data()) + + "' not convertible to the desired type."); + } + } + + return T(); +} + +template<typename T> +void +ConfigTreeNew:: +checkConfParam(std::string const& param, T const& value) +{ + if (getConfParam<T>(param) != value) { + error("The value of key <" + param + "> is not the expected one."); + } +} + +template<typename Ch> +void +ConfigTreeNew:: +checkConfParam(std::string const& param, Ch const* value) +{ + if (getConfParam<std::string>(param) != value) { + error("The value of key <" + param + "> is not the expected one."); + } +} + + +template<typename T> +ConfigTreeNew::CountType& +ConfigTreeNew:: +markVisited(std::string const& key, bool peek_only) +{ + auto const type = std::type_index(typeid(T)); + + auto p = _visited_params.emplace(key, CountType{peek_only ? 0 : 1, type}); + + if (!p.second) { // no insertion happened + auto& v = p.first->second; + if (v.type == type) { + if (!peek_only) ++v.count; + } else { + error("There already was an attempt to obtain key <" + key + + "> with type \"" + v.type.name() + "\" (now: \"" + type.name() + "\")."); + } + } + + return p.first->second; +} + +} diff --git a/BaseLib/ConfigTreeNew.cpp b/BaseLib/ConfigTreeNew.cpp new file mode 100644 index 0000000000000000000000000000000000000000..24b807d883c34582a7c6fa84dfd270b0bdf08bfa --- /dev/null +++ b/BaseLib/ConfigTreeNew.cpp @@ -0,0 +1,224 @@ +/** + * \copyright + * Copyright (c) 2012-2015, 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 "ConfigTreeNew.h" + +namespace BaseLib +{ + +const char ConfigTreeNew::pathseparator = '/'; +const std::string ConfigTreeNew::key_chars_start = "abcdefghijklmnopqrstuvwxyz"; +const std::string ConfigTreeNew::key_chars = key_chars_start + "_0123456789"; + +ConfigTreeNew:: +ConfigTreeNew(PTree const& tree, + Callback const& error_cb, + Callback const& warning_cb) + : _tree(&tree), _onerror(error_cb), _onwarning(warning_cb) +{ + if (!_onerror) { + ERR("ConfigTree: No valid error handler provided."); + std::abort(); + } + if (!_onwarning) { + ERR("ConfigTree: No valid warning handler provided."); + std::abort(); + } +} + +ConfigTreeNew:: +ConfigTreeNew(PTree const& tree, ConfigTreeNew const& parent, + std::string const& root) + : _tree(&tree), _path(joinPaths(parent._path, root)), + _onerror(parent._onerror), _onwarning(parent._onwarning) +{ + checkKeyname(root); +} + +ConfigTreeNew:: +ConfigTreeNew(ConfigTreeNew && other) + : _tree(other._tree) + , _path(other._path) + , _visited_params(std::move(other._visited_params)) + , _onerror(other._onerror) + , _onwarning(other._onwarning) +{ + other._tree = nullptr; +} + +ConfigTreeNew::~ConfigTreeNew() +{ + if (!_tree) return; + + for (auto const& p : *_tree) + { + markVisitedDecrement(p.first); + } + + for (auto const& p : _visited_params) + { + if (p.second.count > 0) { + warning("Key <" + p.first + "> has been read " + std::to_string(p.second.count) + + " time(s) more than it was present in the configuration tree."); + } else if (p.second.count < 0) { + warning("Key <" + p.first + "> has been read " + std::to_string(-p.second.count) + + " time(s) less than it was present in the configuration tree."); + } + } +} + +ConfigTreeNew +ConfigTreeNew:: +getConfSubtree(std::string const& root) +{ + if (auto t = getConfSubtreeOptional(root)) { + return std::move(*t); + } else { + error("Key <" + root + "> has not been found."); + return ConfigTreeNew(PTree(), *this, ""); // TODO that will crash + } +} + +boost::optional<ConfigTreeNew> +ConfigTreeNew:: +getConfSubtreeOptional(std::string const& root) +{ + checkUnique(root); + auto subtree = _tree->get_child_optional(root); + + if (subtree) { + markVisited(root); + return boost::optional<ConfigTreeNew>(std::move( + ConfigTreeNew(*subtree, *this, root))); + } else { + markVisited(root, true); + return boost::optional<ConfigTreeNew>(); + } +} + +Range<ConfigTreeNew::SubtreeIterator> +ConfigTreeNew:: +getConfSubtreeList(std::string const& root) +{ + checkUnique(root); + markVisited(root, true); + + auto p = _tree->equal_range(root); + + return Range<SubtreeIterator>( + SubtreeIterator(p.first, root, *this), + SubtreeIterator(p.second, root, *this)); +} + +void ConfigTreeNew::ignoreConfParam(const std::string ¶m) +{ + checkUnique(param); + // if not found, peek only + bool peek_only = _tree->find(param) == _tree->not_found(); + markVisited(param, peek_only); +} + +void ConfigTreeNew::ignoreConfParamAll(const std::string ¶m) +{ + checkUnique(param); + auto& ct = markVisited(param, true); + + auto p = _tree->equal_range(param); + for (auto it = p.first; it != p.second; ++it) { + ++ct.count; + } +} + + +void ConfigTreeNew::error(const std::string& message) +{ + _onerror(_path, message); +} + +void ConfigTreeNew::warning(const std::string& message) +{ + _onwarning(_path, message); +} + + +void ConfigTreeNew::onerror(const std::string& path, const std::string& message) +{ + ERR("ConfigTree: At path <%s>: %s", path.c_str(), message.c_str()); + std::abort(); +} + +void ConfigTreeNew::onwarning(const std::string& path, const std::string& message) +{ + WARN("ConfigTree: At path <%s>: %s", path.c_str(), message.c_str()); +} + +std::string ConfigTreeNew::shortString(const std::string &s) +{ + const std::size_t maxlen = 100; + + if (s.size() < maxlen) return s; + + return s.substr(0, maxlen-3) + "..."; +} + + +void ConfigTreeNew::checkKeyname(std::string const& key) +{ + if (key.empty()) { + error("Search for empty key."); + } else if (key_chars_start.find(key.front()) == std::string::npos) { + error("Key <" + key + "> starts with an illegal character."); + } else if (key.find_first_not_of(key_chars, 1) != std::string::npos) { + error("Key <" + key + "> contains illegal characters."); + } +} + +std::string ConfigTreeNew:: +joinPaths( const std::string &p1, const std::string &p2) +{ + if (p2.empty()) { + error("Second path to be joined is empty."); + } + + if (p1.empty()) return p2; + + return p1 + pathseparator + p2; +} + +void ConfigTreeNew::checkUnique(const std::string &key) +{ + checkKeyname(key); + + if (_visited_params.find(key) != _visited_params.end()) { + error("Key <" + key + "> has already been processed."); + } +} + +ConfigTreeNew::CountType& +ConfigTreeNew:: +markVisited(std::string const& key, bool peek_only) +{ + return markVisited<ConfigTreeNew>(key, peek_only); +} + +void +ConfigTreeNew:: +markVisitedDecrement(std::string const& key) +{ + auto const type = std::type_index(typeid(nullptr)); + + auto p = _visited_params.emplace(key, CountType{-1, type}); + + if (!p.second) { // no insertion happened + auto& v = p.first->second; + --v.count; + } +} + +} diff --git a/BaseLib/ConfigTreeNew.h b/BaseLib/ConfigTreeNew.h new file mode 100644 index 0000000000000000000000000000000000000000..ea660c36d7b9291cee14d8ee21c62c113a3099e5 --- /dev/null +++ b/BaseLib/ConfigTreeNew.h @@ -0,0 +1,392 @@ +/** + * \copyright + * Copyright (c) 2012-2015, OpenGeoSys Community (http://www.opengeosys.org) + * Distributed under a Modified BSD License. + * See accompanying file LICENSE.txt or + * http://www.opengeosys.org/project/license + * + */ + +#pragma once + +#include "ConfigTree.h" + +#include <typeindex> +#include <map> + +#include <functional> + +namespace BaseLib +{ + +template<typename Iterator> class Range; + +/*! + * Wrapper around a Boost Property Tree with some basic error reporting features. + * + * Features. This class: + * * makes sure that every configuration setting in a Property Tree is read + * exactly once. If some settings is not read (e.g. due to a typo), a warning message + * is generated. The message contains a hint where it occured. + * * enforces a naming scheme of settings: letters a-z, numbers 0-9, underscore + * * provides some functionality to read lists of values using range-based for loops. + * * has rather long method names that are easily greppable from the source code. So a list + * of supported configuration options can be easily obtained from the source code. + * + * The purpose of this class is to reduce or completely avoid the amount of error-handling + * code in routines that take configuration parameters. + * + * Most methods of this class check that they have not been called before for the same + * \c ConfigTree and the same parameter. This behaviour helps to enforce that every parameter + * is read exactly once during parsing of the configuration settings. + * + * The most notable restriction of this class when compared to plain tree traversal is, that + * one must know all the XML tags (i.e. configuration parameters) at compile time. It is not + * possible to read from this class, which configuration parameters are present in the tree. + * This restriction, however, is intended, because it provides the possibility to get all + * existing configuration parameters from the source code. + */ +class ConfigTreeNew final +{ +public: + /*! + * A wrapper around a Boost Iterator for iterating over ranges of subtrees. + * + * The methods of this class tell the associated (parent) \c ConfigTree object when + * a setting has been parsed. + */ + class SubtreeIterator + : public std::iterator<std::input_iterator_tag, ConfigTreeNew> + { + public: + using Iterator = boost::property_tree::ptree::const_assoc_iterator; + + explicit SubtreeIterator(Iterator it, std::string const& root, + ConfigTreeNew& parent) + : _it(it), _root(root), _parent(parent) + {} + + SubtreeIterator& operator++() { + ++_it; + _has_incremented = true; + return *this; + } + + ConfigTreeNew operator*() { + // if this iterator has been incremented since the last dereference, + // tell the _parent instance that a subtree now has been parsed. + if (_has_incremented) { + _has_incremented = false; + _parent.markVisited(_root); + } + return ConfigTreeNew(_it->second, _parent, _root); + } + + bool operator==(SubtreeIterator const& other) const { + return _it == other._it; + } + + bool operator!=(SubtreeIterator const& other) const { + return _it != other._it; + } + + private: + bool _has_incremented = true; + Iterator _it; + std::string const _root; + ConfigTreeNew& _parent; + }; + + + /*! + * A wrapper around a Boost Iterator for iterating over ranges of values. + * + * The methods of this class tell the associated (parent) \c ConfigTree object when + * a setting has been parsed. + */ + template<typename ValueType> + class ValueIterator + : public std::iterator<std::input_iterator_tag, ValueType> + { + public: + using Iterator = boost::property_tree::ptree::const_assoc_iterator; + + explicit ValueIterator(Iterator it, std::string const& root, + ConfigTreeNew& parent) + : _it(it), _root(root), _parent(parent) + {} + + ValueIterator<ValueType>& operator++() { + ++_it; + _has_incremented = true; + return *this; + } + + ValueType operator*() { + // if this iterator has been incremented since the last dereference, + // tell the _parent instance that a setting now has been parsed. + if (_has_incremented) { + _has_incremented = false; + _parent.markVisited<ValueType>(_root); + } + + if (_it->second.begin() != _it->second.end()) { + _parent.error("Configuration at key " + _root + " has subitems."); + return ValueType(); + } + + auto v = _it->second.get_value_optional<ValueType>(); + + if (v) return *v; + + // TODO: change error method + _parent.error("Could not get value out of key " + _root + "."); + return ValueType(); + } + + bool operator==(ValueIterator<ValueType> const& other) const { + return _it == other._it; + } + + bool operator!=(ValueIterator<ValueType> const& other) const { + return _it != other._it; + } + + private: + bool _has_incremented = true; + Iterator _it; + std::string const _root; + ConfigTreeNew& _parent; + }; + + using PTree = boost::property_tree::ptree; + + //! Type of the function objects used as callbacks. + //! The first argument denotes the path in the tree at which an event (warning/error) + //! occured, the second argument is the associated message + using Callback = std::function<void(const std::string& path, + const std::string& message)>; + + /*! Creates a new instance wrapping the given Boost Property Tree. + * + * \param tree the Boost Property Tree to be wrapped + * \param error_cb callback function to be called on error. + * \param warning_cb callback function to be called on warning. + * + * The callback functions must be valid callable functions, i.e. not nullptr's. + * They are configurable in order to make unit tests of this class easier. + * They should not be provided in production code! + * + * If a custom error callback is provided, this function should break out of + * the normal execution order, e.g., by throwing or by calling std::abort(), + * because otherwise this class will effectively treat errors as no-errors. + */ + explicit ConfigTreeNew(PTree const& tree, + Callback const& error_cb = onerror, + Callback const& warning_cb = onwarning); + + //! copying is not compatible with the semantics of this class + ConfigTreeNew(ConfigTreeNew const&) = delete; + + //! After being moved from, \c other is in an undefined state and must not be + //! used anymore! + ConfigTreeNew(ConfigTreeNew && other); + + ConfigTreeNew() = delete; + + void operator=(ConfigTreeNew const&) = delete; + void operator=(ConfigTreeNew &&) = delete; + + /*! Get parameter \c param of type \c T from the configuration tree. + * + * \return the value looked for or a default constructed value \c T() in the case of + * an error. For the behaviour in case of an error, see also the documentation + * of the method error(). + * + * \pre \c param must not have been read before from this ConfigTree. + */ + template<typename T> T + getConfParam(std::string const& param); + + /*! Get parameter \c param of type \c T from the configuration tree or the \c default_value. + * + * This method has a similar behaviour as getConfParam(std::string const&) except in case + * of errors the \c default_value is returned. + * + * \pre \c param must not have been read before from this ConfigTree. + */ + template<typename T> T + getConfParam(std::string const& param, T const& default_value); + + /*! Get parameter \c param of type \c T from the configuration tree if present + * + * This method has a similar behaviour as getConfParam(std::string const&) except + * no errors are raised. Rather it can be told from the return value if the + * parameter could be read. + * + * \pre \c param must not have been read before from this ConfigTree. + */ + template<typename T> boost::optional<T> + getConfParamOptional(std::string const& param); + + /*! Returns all parameters with the name \c param from the current level of the tree. + * + * The return value is suitable to be used with range-base for-loops. + * + * \pre \c param must not have been read before from this ConfigTree. + */ + template<typename T> Range<ValueIterator<T> > + getConfParamList(std::string const& param); + + /*! Peek at a parameter \c param of type \c T from the configuration tree. + * + * This method is an exception to the single-read rule. It is meant to be used to + * tell from a ConfigTree instance where to pass that instance on for further processing. + * + * Return value and error behaviour are the same as for getConfParam(std::string const&). + */ + template<typename T> T + peekConfParam(std::string const& param); + + /*! Assert that \c param has the given \c value. + * + * Convenience method combining getConfParam(std::string const&) with a check. + */ + template<typename T> void + checkConfParam(std::string const& param, T const& value); + + //! Make checkConfParam() work for string literals. + template<typename Ch> void + checkConfParam(std::string const& param, Ch const* value); + + /*! Get the subtree rooted at \c root + * + * If \c root is not found error() is called. + * + * \pre \c root must not have been read before from this ConfigTree. + */ + ConfigTreeNew + getConfSubtree(std::string const& root); + + /*! Get the subtree rooted at \c root if present + * + * \pre \c root must not have been read before from this ConfigTree. + */ + boost::optional<ConfigTreeNew> + getConfSubtreeOptional(std::string const& root); + + /*! Get all subtrees that have a root \c root from the current level of the tree. + * + * The return value is suitable to be used with range-base for-loops. + * + * \pre \c root must not have been read before from this ConfigTree. + */ + Range<SubtreeIterator> + getConfSubtreeList(std::string const& root); + + /*! Tell this instance to ignore parameter \c param. + * + * This method is used to avoid warning messages. + * + * \pre \c root must not have been read before from this ConfigTree. + */ + void ignoreConfParam(std::string const& param); + + /*! Tell this instance to ignore all parameters \c param on the current level of the tree. + * + * This method is used to avoid warning messages. + * + * \pre \c root must not have been read before from this ConfigTree. + */ + void ignoreConfParamAll(std::string const& param); + + //! The destructor performs the check if all nodes at the current level of the tree + //! have been read. + ~ConfigTreeNew(); + +private: + struct CountType + { + int count; + std::type_index type; + }; + + //! Used for wrapping a subtree + explicit ConfigTreeNew(PTree const& tree, ConfigTreeNew const& parent, std::string const& root); + + //! Called if an error occurs. Will call the error callback. + //! This method only acts as a helper method. + void error(std::string const& message); + + //! Called for printing warning messages. Will call the warning callback. + //! This method only acts as a helper method. + void warning(std::string const& message); + + //! Checks if \c key complies with the rules [a-z0-9_]. + void checkKeyname(std::string const& key); + + //! Used to generate the path of a subtree. + std::string joinPaths(std::string const& p1, std::string const& p2); + + //! Asserts that the \c key has not been read yet. + void checkUnique(std::string const& key); + + /*! Keeps track of the key \c key and its value type \c T. + * + * This method asserts that a key is read always with the same type. + * + * \c param peek_only if true, do not change the read-count of the given key. + */ + template<typename T> + CountType& markVisited(std::string const& key, bool peek_only = false); + + /*! Keeps track of the key \c key and its value type ConfigTree. + * + * This method asserts that a key is read always with the same type. + * + * \c param peek_only if true, do not change the read-count of the given key. + */ + CountType& markVisited(std::string const& key, bool peek_only = false); + + //! Used in the destructor to compute the difference between number of reads of a parameter + //! and the number of times it exists in the ConfigTree + void markVisitedDecrement(std::string const& key); + + //! Default error callback function + //! Will print an error message and call std::abort() + static void onerror(std::string const& path, std::string const& message); + + //! Default warning callback function + //! Will print a warning message + static void onwarning(std::string const& path, std::string const& message); + + //! returns a short string at suitable for error/warning messages + static std::string shortString(std::string const& s); + + //! The wrapped tree. + boost::property_tree::ptree const* _tree; + + //! A path printed in error/warning messages. + std::string const _path; + + //! A map key -> (count, type) keeping track which parameters have been read how often + //! and which datatype they have. + std::map<std::string, CountType> _visited_params; + + const Callback _onerror; + const Callback _onwarning; + + //! Character separating two path components. + static const char pathseparator; + + //! Set of allowed characters as the first letter of a key name. + static const std::string key_chars_start; + + //! Set of allowed characters in a key name. + static const std::string key_chars; +}; + +} + +#include "ConfigTreeNew-impl.h" + diff --git a/CHANGELOG.md b/CHANGELOG.md index 7719a8be4f34f9e48de036d93fc154c366857ca8..45b38e1afa99ebe7b1161b3a5b61800371cae120 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,9 @@ - Axis aligned bounding box: - Is now a from the right half-open interval. - Removed template from class declaration. + - New configuration tree parser + - Checks configuration parameters more strictly, automatically prints error/warning messages. + - Requires Boost >= 1.56 because of boost::optional with move semantics. ### Infrastructure diff --git a/ProcessLib/InitialCondition.cpp b/ProcessLib/InitialCondition.cpp index bad0bb68a43aae732c757e30104f5fe32a9d8401..ff0789664f198d420a759bc8897da15235e9c60e 100644 --- a/ProcessLib/InitialCondition.cpp +++ b/ProcessLib/InitialCondition.cpp @@ -16,46 +16,40 @@ #include "MeshLib/Elements/Element.h" #include "MeshLib/Mesh.h" +#include "BaseLib/ConfigTreeNew.h" + namespace ProcessLib { std::unique_ptr<InitialCondition> createUniformInitialCondition( - BaseLib::ConfigTree const& config) + BaseLib::ConfigTreeNew& config) { - auto value = config.get_optional<double>("value"); - if (!value) - { - ERR("Could not find required parameter value."); - std::abort(); - } - DBUG("Using value %g", *value); + config.checkConfParam("type", "Uniform"); + + auto value = config.getConfParam<double>("value"); + DBUG("Using value %g", value); return std::unique_ptr<InitialCondition>( - new UniformInitialCondition(*value)); + new UniformInitialCondition(value)); } std::unique_ptr<InitialCondition> createMeshPropertyInitialCondition( - BaseLib::ConfigTree const& config, MeshLib::Mesh const& mesh) + BaseLib::ConfigTreeNew& config, MeshLib::Mesh const& mesh) { - auto field_name = config.get_optional<std::string>("field_name"); - if (!field_name) - { - ERR("Could not find required parameter field_name."); - std::abort(); - } - DBUG("Using field_name %s", field_name->c_str()); + auto field_name = config.getConfParam<std::string>("field_name"); + DBUG("Using field_name %s", field_name.c_str()); - if (!mesh.getProperties().hasPropertyVector(*field_name)) + if (!mesh.getProperties().hasPropertyVector(field_name)) { ERR("The required property %s does not exists in the mesh.", - field_name->c_str()); + field_name.c_str()); std::abort(); } auto const& property = - mesh.getProperties().template getPropertyVector<double>(*field_name); + mesh.getProperties().template getPropertyVector<double>(field_name); if (!property) { ERR("The required property %s is not of the requested type.", - field_name->c_str()); + field_name.c_str()); std::abort(); } diff --git a/ProcessLib/InitialCondition.h b/ProcessLib/InitialCondition.h index b3093bc39308d8524d004b38cfa215e56e2d8ae7..b8a6831cdf80f6ff40ae5fdf01cf58300ad4348e 100644 --- a/ProcessLib/InitialCondition.h +++ b/ProcessLib/InitialCondition.h @@ -15,6 +15,11 @@ #include "MeshLib/Node.h" #include "MeshLib/PropertyVector.h" +namespace BaseLib +{ +class ConfigTreeNew; +} + namespace MeshLib { template <typename> @@ -52,7 +57,7 @@ private: /// Construct a UniformInitialCondition from configuration. std::unique_ptr<InitialCondition> createUniformInitialCondition( - BaseLib::ConfigTree const& config); + BaseLib::ConfigTreeNew& config); /// Distribution of values given by a mesh property defined on nodes. class MeshPropertyInitialCondition : public InitialCondition @@ -76,7 +81,7 @@ private: /// Construct a MeshPropertyInitialCondition from configuration. std::unique_ptr<InitialCondition> createMeshPropertyInitialCondition( - BaseLib::ConfigTree const& config, MeshLib::Mesh const& mesh); + BaseLib::ConfigTreeNew& config, MeshLib::Mesh const& mesh); } // namespace ProcessLib diff --git a/ProcessLib/NeumannBcConfig.h b/ProcessLib/NeumannBcConfig.h index 5cccdadb7ff782735bd562bf93a81ea1d7ce0300..e65d2d972258aa1d3fc310ac5f2cec3209750fe8 100644 --- a/ProcessLib/NeumannBcConfig.h +++ b/ProcessLib/NeumannBcConfig.h @@ -12,7 +12,7 @@ #include "logog/include/logog.hpp" -#include "BaseLib/ConfigTree.h" +#include "BaseLib/ConfigTreeNew.h" #include "MathLib/ConstantFunction.h" #include "MeshGeoToolsLib/BoundaryElementsSearcher.h" #include "MeshLib/Elements/Element.h" @@ -44,12 +44,13 @@ class NeumannBcConfig : public BoundaryConditionConfig { public: NeumannBcConfig(GeoLib::GeoObject const* const geometry, - BaseLib::ConfigTree const& config) + BaseLib::ConfigTreeNew& config) : BoundaryConditionConfig(geometry) { DBUG("Constructing NeumannBcConfig from config."); + config.checkConfParam("type", "UniformNeumann"); - double const value = config.get<double>("value", 0); + double const value = config.getConfParam<double>("value"); DBUG("Using value %g", value); _function = new MathLib::ConstantFunction<double>(value); diff --git a/ProcessLib/ProcessVariable.cpp b/ProcessLib/ProcessVariable.cpp index 8ccf66315e9928ff02aacd47d88e577d85db6daa..58b3a083f5e27f72b22c46cbafca69a74c81ffff 100644 --- a/ProcessLib/ProcessVariable.cpp +++ b/ProcessLib/ProcessVariable.cpp @@ -14,54 +14,52 @@ #include "GeoLib/GEOObjects.h" #include "MeshLib/Mesh.h" +#include "BaseLib/ConfigTreeNew.h" + namespace ProcessLib { -ProcessVariable::ProcessVariable(BaseLib::ConfigTree const& config, +ProcessVariable::ProcessVariable(BaseLib::ConfigTreeNew& config, MeshLib::Mesh const& mesh, GeoLib::GEOObjects const& geometries) - : _name(config.get<std::string>("name")), _mesh(mesh) + : _name(config.getConfParam<std::string>("name")) + , _mesh(mesh) { DBUG("Constructing process variable %s", this->_name.c_str()); // Initial condition + if (auto ic_config = config.getConfSubtreeOptional("initial_condition")) { - auto const& ic_config = config.find("initial_condition"); - if (ic_config == config.not_found()) - INFO("No initial condition found."); - - std::string const type = - config.get<std::string>("initial_condition.type"); + auto const type = ic_config->peekConfParam<std::string>("type"); if (type == "Uniform") { _initial_condition = - createUniformInitialCondition(ic_config->second); + createUniformInitialCondition(*ic_config); } else if (type == "MeshProperty") { _initial_condition = - createMeshPropertyInitialCondition(ic_config->second, _mesh); + createMeshPropertyInitialCondition(*ic_config, _mesh); } else { ERR("Unknown type of the initial condition."); } } + else + { + INFO("No initial condition found."); + } // Boundary conditions + if (auto bcs_config = config.getConfSubtreeOptional("boundary_conditions")) { - auto const& bcs_config = config.find("boundary_conditions"); - if (bcs_config == config.not_found()) - INFO("No boundary conditions found."); - - for (auto const& bc_iterator : bcs_config->second) + for (auto bc_config + : bcs_config->getConfSubtreeList("boundary_condition")) { - BaseLib::ConfigTree const& bc_config = bc_iterator.second; - - // Find corresponding GeoObject - std::string const geometrical_set_name = - bc_config.get<std::string>("geometrical_set"); - std::string const geometry_name = - bc_config.get<std::string>("geometry"); + auto const geometrical_set_name = + bc_config.getConfParam<std::string>("geometrical_set"); + auto const geometry_name = + bc_config.getConfParam<std::string>("geometry"); GeoLib::GeoObject const* const geometry = geometries.getGeoObject(geometrical_set_name, geometry_name); @@ -70,7 +68,7 @@ ProcessVariable::ProcessVariable(BaseLib::ConfigTree const& config, GeoLib::convertGeoTypeToString(geometry->getGeoType()).c_str()); // Construct type dependent boundary condition - std::string const type = bc_config.get<std::string>("type"); + auto const type = bc_config.peekConfParam<std::string>("type"); if (type == "UniformDirichlet") { @@ -88,7 +86,12 @@ ProcessVariable::ProcessVariable(BaseLib::ConfigTree const& config, type.c_str()); } } + } else { + INFO("No boundary conditions found."); } + + // Source Terms + config.ignoreConfParam("source_terms"); } ProcessVariable::ProcessVariable(ProcessVariable&& other) diff --git a/ProcessLib/ProcessVariable.h b/ProcessLib/ProcessVariable.h index f83b61e760166b91181508fa4b193697aca26c3b..957cdc45f6d771aef0fe41da70145bd8677affba 100644 --- a/ProcessLib/ProcessVariable.h +++ b/ProcessLib/ProcessVariable.h @@ -46,7 +46,7 @@ namespace ProcessLib class ProcessVariable { public: - ProcessVariable(BaseLib::ConfigTree const& config, MeshLib::Mesh const& mesh, + ProcessVariable(BaseLib::ConfigTreeNew& config, MeshLib::Mesh const& mesh, GeoLib::GEOObjects const& geometries); ProcessVariable(ProcessVariable&&); diff --git a/ProcessLib/UniformDirichletBoundaryCondition.h b/ProcessLib/UniformDirichletBoundaryCondition.h index 5eed826238821c2572b273205357b8e969600005..2275e49d4f97162f54546bb6aa50cd6e578031de 100644 --- a/ProcessLib/UniformDirichletBoundaryCondition.h +++ b/ProcessLib/UniformDirichletBoundaryCondition.h @@ -17,7 +17,7 @@ #include "NumericsConfig.h" // for GlobalIndexType -#include "BaseLib/ConfigTree.h" +#include "BaseLib/ConfigTreeNew.h" #include "AssemblerLib/LocalToGlobalIndexMap.h" #include "MeshGeoToolsLib/MeshNodeSearcher.h" @@ -37,12 +37,13 @@ class UniformDirichletBoundaryCondition { public: UniformDirichletBoundaryCondition(GeoLib::GeoObject const* const geometry, - BaseLib::ConfigTree const& config) + BaseLib::ConfigTreeNew& config) : _geometry(geometry) { DBUG("Constructing UniformDirichletBoundaryCondition from config."); + config.checkConfParam("type", "UniformDirichlet"); - _value = config.get<double>("value", 0); + _value = config.getConfParam<double>("value"); DBUG("Using value %g", _value); } diff --git a/Tests/BaseLib/TestConfigTree.cpp b/Tests/BaseLib/TestConfigTree.cpp new file mode 100644 index 0000000000000000000000000000000000000000..ffdf50ddf555b4b4be435a04641aa2ed0cb3f33a --- /dev/null +++ b/Tests/BaseLib/TestConfigTree.cpp @@ -0,0 +1,430 @@ +/** + * \copyright + * Copyright (c) 2012-2015, 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 <gtest/gtest.h> +#include <logog/include/logog.hpp> + +#include <boost/property_tree/xml_parser.hpp> +#include <sstream> + +#include "BaseLib/ConfigTreeNew.h" + +#define DO_EXPECT(cbs, error, warning) do { \ + if (error) EXPECT_TRUE((cbs).get_error()); else EXPECT_FALSE((cbs).get_error()); \ + if (warning) EXPECT_TRUE((cbs).get_warning()); else EXPECT_FALSE((cbs).get_warning()); \ + (cbs).reset(); \ + } while(false) + +#define RUN_SAFE(expr) do { \ + try { expr; } catch(Exc) {} \ + } while (false) + +// Exception thrown by the error callback of the class below +class Exc {}; + +// class that provides callback functions used with ConfigTreeNew +class Callbacks +{ +public: + BaseLib::ConfigTreeNew::Callback + get_error_cb() { + return [this](std::string const& path, std::string const& message) + { + (void) path; (void) message; + DBUG("error <%s> : %s", path.c_str(), message.c_str()); + _error = true; + throw Exc(); // throw in order to stop normal execution + }; + } + + BaseLib::ConfigTreeNew::Callback + get_warning_cb() { + return [this](std::string const& path, std::string const& message) + { + (void) path; (void) message; + DBUG("warning <%s> : %s", path.c_str(), message.c_str()); + _warning = true; + }; + } + + bool get_error() const { return _error; } + bool get_warning() const { return _warning; } + void reset() { _error = false; _warning = false; } + +private: + bool _error = false; + bool _warning = false; +}; + + +TEST(BaseLibConfigTree, ConfigTreeEmpty) +{ + boost::property_tree::ptree ptree; + Callbacks cbs; + + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + (void) conf; + } // ConfigTree destroyed here + + DO_EXPECT(cbs, false, false); +} + + +TEST(BaseLibConfigTree, ConfigTreeGet) +{ + const char xml[] = + "<double>5.6e-4</double>" + "<bool>true</bool>" + "<int>5</int>" + "<sub>" + " <float>6.1</float>" + " <float2>0.1</float2>" + " <ignored/>" + " <ignored2/>" + " <ignored2/>" + "</sub>" + "<x>Y</x>"; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + EXPECT_EQ(5.6e-4, conf.getConfParam<double>("double")); // read certain types + DO_EXPECT(cbs, false, false); + EXPECT_TRUE(conf.getConfParam<bool>("bool")); + DO_EXPECT(cbs, false, false); + EXPECT_EQ(5, conf.getConfParam<int>("int")); + DO_EXPECT(cbs, false, false); + + EXPECT_EQ(8, conf.getConfParam<int>("intx", 8)); // reading with default value + DO_EXPECT(cbs, false, false); + + { + auto sub = conf.getConfSubtree("sub"); + DO_EXPECT(cbs, false, false); + + EXPECT_EQ(6.1f, sub.getConfParam<float>("float")); + DO_EXPECT(cbs, false, false); + + if (auto f2 = sub.getConfParamOptional<float>("float2")) { // read optional value + EXPECT_EQ(0.1f, *f2); + DO_EXPECT(cbs, false, false); + } + + auto f3 = sub.getConfParamOptional<float>("float3"); // optional value not existent + ASSERT_FALSE(f3); + DO_EXPECT(cbs, false, false); + + sub.ignoreConfParam("ignored"); + DO_EXPECT(cbs, false, false); + sub.ignoreConfParamAll("ignored2"); + DO_EXPECT(cbs, false, false); + sub.ignoreConfParamAll("ignored4"); // I can ignore nonexistent stuff + DO_EXPECT(cbs, false, false); + + // I can not ignore stuff that I already read + // this also makes sure that the subtree inherits the callbacks properly + RUN_SAFE(sub.ignoreConfParam("float")); + DO_EXPECT(cbs, true, false); + } + for (int i : {0, 1, 2}) { + (void) i; + EXPECT_EQ("Y", conf.peekConfParam<std::string>("x")); + DO_EXPECT(cbs, false, false); + } + conf.checkConfParam<std::string>("x", "Y"); + DO_EXPECT(cbs, false, false); + } // ConfigTree destroyed here + DO_EXPECT(cbs, false, false); +} + + +TEST(BaseLibConfigTree, ConfigTreeIncompleteParse) +{ + const char xml[] = + "<double>5.6</double>" + "<bool>true</bool>" + ; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + EXPECT_EQ(5.6, conf.getConfParam<double>("double")); // read certain types + DO_EXPECT(cbs, false, false); + } // ConfigTree destroyed here + DO_EXPECT(cbs, false, true); // expect warning because I didn't read everything +} + + +TEST(BaseLibConfigTree, ConfigTreeCheckRange) +{ + const char xml[] = + "<val><int>0</int></val>" + "<val><int>1</int></val>" + "<val><int>2</int></val>" + "<int>0</int>" + "<int>1</int>" + "<int>2</int>"; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + { + auto list = conf.getConfSubtreeList("val"); + DO_EXPECT(cbs, false, false); + EXPECT_EQ(3, std::distance(list.begin(), list.end())); + DO_EXPECT(cbs, false, false); + EXPECT_EQ(3, std::distance(list.begin(), list.end())); + DO_EXPECT(cbs, false, false); + } + + { + auto list = conf.getConfParamList<int>("int"); + DO_EXPECT(cbs, false, false); + EXPECT_EQ(3, std::distance(list.begin(), list.end())); + DO_EXPECT(cbs, false, false); + EXPECT_EQ(3, std::distance(list.begin(), list.end())); + DO_EXPECT(cbs, false, false); + } + + } // ConfigTree destroyed here + + // there will be warnings because I don't process the list entries + DO_EXPECT(cbs, false, true); +} + + +TEST(BaseLibConfigTree, ConfigTreeGetSubtreeList) +{ + const char xml[] = + "<val><int>0</int></val>" + "<val><int>1</int></val>" + "<val><int>2</int></val>"; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + int i = 0; + for (auto ct : conf.getConfSubtreeList("val")) + { + EXPECT_EQ(i, ct.getConfParam<int>("int")); + DO_EXPECT(cbs, false, false); + ++i; + } + } // ConfigTree destroyed here + DO_EXPECT(cbs, false, false); +} + + +TEST(BaseLibConfigTree, ConfigTreeGetValueList) +{ + const char xml[] = + "<int>0</int>" + "<int>1</int>" + "<int>2</int>"; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + int n = 0; + for (auto i : conf.getConfParamList<int>("int")) + { + EXPECT_EQ(n, i); + DO_EXPECT(cbs, false, false); + ++n; + } + } // ConfigTree destroyed here + DO_EXPECT(cbs, false, false); +} + + +TEST(BaseLibConfigTree, ConfigTreeNoConversion) +{ + const char xml[] = + "<int>5.6</int>" // not convertible to int + "<double>5.6tz</double>" // not convertible to double + "<non_double>0.1x</non_double>" // not either convertible to double + "<bool>true</bool>" + "<ign/>" + "<ign2/><ign2/><ign2/>" + ; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + RUN_SAFE(conf.getConfParam<int>("int")); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.ignoreConfParam("int")); // after failure I also cannot ignore something + DO_EXPECT(cbs, true, false); + + RUN_SAFE(conf.getConfParam<double>("double")); + DO_EXPECT(cbs, true, false); + + // peek value existent but not convertible + RUN_SAFE(conf.peekConfParam<double>("non_double")); + DO_EXPECT(cbs, true, false); + + // optional value existent but not convertible + RUN_SAFE( + auto d = conf.getConfParamOptional<double>("non_double"); + ASSERT_FALSE(d); + ); + DO_EXPECT(cbs, true, false); + + // assert that I can only ignore something once + RUN_SAFE(conf.ignoreConfParam("ign")); + DO_EXPECT(cbs, false, false); + RUN_SAFE(conf.ignoreConfParam("ign")); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.ignoreConfParamAll("ign2")); + DO_EXPECT(cbs, false, false); + RUN_SAFE(conf.ignoreConfParamAll("ign2")); + DO_EXPECT(cbs, true, false); + + // assert that I cannot read a parameter twice + RUN_SAFE(conf.getConfParam<bool>("bool")); + DO_EXPECT(cbs, false, false); + RUN_SAFE(conf.getConfParam<bool>("bool")); + DO_EXPECT(cbs, true, false); + + } // ConfigTree destroyed here + + // There will bewarnings because I don't succeed in reading every setting, + // and furthermore I read some setting too often. + DO_EXPECT(cbs, false, false); +} + + +TEST(BaseLibConfigTree, BadKeynames) +{ + const char xml[] = ""; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + for (std::string tag : { "<", "Z", ".", "$", "0", "", "/" }) + { + RUN_SAFE(conf.getConfParam<int>(tag)); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.getConfParam<int>(tag, 500)); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.getConfParamOptional<int>(tag)); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.getConfParamList<int>(tag)); + + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.peekConfParam<int>(tag)); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.checkConfParam<int>(tag, 500)); + + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.getConfSubtree(tag)); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.getConfSubtreeOptional(tag)); + DO_EXPECT(cbs, true, false); + RUN_SAFE(conf.getConfSubtreeList(tag)); + DO_EXPECT(cbs, true, false); + } + + } // ConfigTree destroyed here + + DO_EXPECT(cbs, false, false); +} + +// String literals are somewhat special for template classes +TEST(BaseLibConfigTree, ConfigTreeStringLiterals) +{ + const char xml[] = + "<s>test</s>" + "<t>Test</t>"; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + EXPECT_EQ("test", conf.getConfParam<std::string>("s", "XX")); + DO_EXPECT(cbs, false, false); + + EXPECT_EQ("XX", conf.getConfParam<std::string>("n", "XX")); + DO_EXPECT(cbs, false, false); + + conf.checkConfParam("t", "Test"); + DO_EXPECT(cbs, false, false); + } // ConfigTree destroyed here + DO_EXPECT(cbs, false, false); +} + +// String literals are somewhat special for template classes +TEST(BaseLibConfigTree, ConfigTreeMove) +{ + const char xml[] = + "<s>test</s>" + "<t>Test</t>"; + + boost::property_tree::ptree ptree; + std::istringstream xml_str(xml); + read_xml(xml_str, ptree); + + Callbacks cbs; + { + BaseLib::ConfigTreeNew conf(ptree, cbs.get_error_cb(), cbs.get_warning_cb()); + + EXPECT_EQ("test", conf.getConfParam<std::string>("s", "XX")); + DO_EXPECT(cbs, false, false); + + BaseLib::ConfigTreeNew conf2(std::move(conf)); + + EXPECT_EQ("XX", conf2.getConfParam<std::string>("n", "XX")); + DO_EXPECT(cbs, false, false); + + conf2.checkConfParam("t", "Test"); + DO_EXPECT(cbs, false, false); + } // ConfigTree destroyed here + DO_EXPECT(cbs, false, false); +} +