OGS
ConfigTree.cpp
Go to the documentation of this file.
1 
11 #include "ConfigTree.h"
12 
13 #include <forward_list>
14 #include <utility>
15 
16 #include "Error.h"
17 #include "Logging.h"
18 
19 // Explicitly instantiate the boost::property_tree::ptree which is a typedef to
20 // the following basic_ptree.
21 template class boost::property_tree::basic_ptree<std::string, std::string,
22  std::less<>>;
23 
26 static std::forward_list<std::string> configtree_destructor_error_messages;
27 
28 namespace BaseLib
29 {
30 const char ConfigTree::pathseparator = '/';
31 const std::string ConfigTree::key_chars_start = "abcdefghijklmnopqrstuvwxyz";
32 const std::string ConfigTree::key_chars = key_chars_start + "_0123456789";
33 
35  std::string filename,
36  Callback error_cb,
37  Callback warning_cb)
38  : top_level_tree_(std::make_shared<PTree>(std::move(top_level_tree))),
39  tree_(top_level_tree_.get()),
40  filename_(std::move(filename)),
41  onerror_(std::move(error_cb)),
42  onwarning_(std::move(warning_cb))
43 {
44  if (!onerror_)
45  {
46  OGS_FATAL("ConfigTree: No valid error handler provided.");
47  }
48  if (!onwarning_)
49  {
50  OGS_FATAL("ConfigTree: No valid warning handler provided.");
51  }
52 }
53 
54 ConfigTree::ConfigTree(PTree const& tree, ConfigTree const& parent,
55  std::string const& root)
56  : top_level_tree_(parent.top_level_tree_),
57  tree_(&tree),
58  path_(joinPaths(parent.path_, root)),
59  filename_(parent.filename_),
60  onerror_(parent.onerror_),
61  onwarning_(parent.onwarning_)
62 {
63  checkKeyname(root);
64 }
65 
67  : top_level_tree_(std::move(other.top_level_tree_)),
68  tree_(other.tree_),
69  path_(std::move(other.path_)),
70  filename_(std::move(other.filename_)),
71  visited_params_(std::move(other.visited_params_)),
72  have_read_data_(other.have_read_data_),
73  onerror_(std::move(other.onerror_)),
74  onwarning_(std::move(other.onwarning_))
75 {
76  other.tree_ = nullptr;
77 }
78 
80 {
81  if (std::uncaught_exceptions() > 0)
82  {
83  /* If the stack unwinds the check below shall be suppressed in order to
84  * not accumulate false-positive configuration errors.
85  */
86  return;
87  }
88 
89  try
90  {
92  }
93  catch (std::exception& e)
94  {
95  ERR("{:s}", e.what());
96  configtree_destructor_error_messages.push_front(e.what());
97  }
98 }
99 
101 {
103 
104  top_level_tree_ = std::move(other.top_level_tree_);
105  tree_ = other.tree_;
106  other.tree_ = nullptr;
107  path_ = std::move(other.path_);
108  filename_ = std::move(other.filename_);
109  visited_params_ = std::move(other.visited_params_);
110  have_read_data_ = other.have_read_data_;
111  onerror_ = std::move(other.onerror_);
112  onwarning_ = std::move(other.onwarning_);
113 
114  return *this;
115 }
116 
117 ConfigTree ConfigTree::getConfigParameter(std::string const& root) const
118 {
119  auto ct = getConfigSubtree(root);
120  if (ct.hasChildren())
121  {
122  error("Requested parameter <" + root + "> actually is a subtree.");
123  }
124  return ct;
125 }
126 
127 std::optional<ConfigTree> ConfigTree::getConfigParameterOptional(
128  std::string const& root) const
129 {
130  auto ct = getConfigSubtreeOptional(root);
131  if (ct && ct->hasChildren())
132  {
133  error("Requested parameter <" + root + "> actually is a subtree.");
134  }
135  return ct;
136 }
137 
139  const std::string& param) const
140 {
141  checkUnique(param);
142  markVisited(param, Attr::TAG, true);
143 
144  auto p = tree_->equal_range(param);
145 
146  return Range<ParameterIterator>(ParameterIterator(p.first, param, *this),
147  ParameterIterator(p.second, param, *this));
148 }
149 
150 ConfigTree ConfigTree::getConfigSubtree(std::string const& root) const
151 {
152  if (auto t = getConfigSubtreeOptional(root))
153  {
154  return std::move(*t);
155  }
156  error("Key <" + root + "> has not been found.");
157 }
158 
159 std::optional<ConfigTree> ConfigTree::getConfigSubtreeOptional(
160  std::string const& root) const
161 {
162  checkUnique(root);
163 
164  if (auto subtree = tree_->get_child_optional(root))
165  {
166  markVisited(root, Attr::TAG, false);
167  return ConfigTree(*subtree, *this, root);
168  }
169  markVisited(root, Attr::TAG, true);
170  return std::nullopt;
171 }
172 
174  std::string const& root) const
175 {
176  checkUnique(root);
177  markVisited(root, Attr::TAG, true);
178 
179  auto p = tree_->equal_range(root);
180 
181  return Range<SubtreeIterator>(SubtreeIterator(p.first, root, *this),
182  SubtreeIterator(p.second, root, *this));
183 }
184 
185 void ConfigTree::ignoreConfigParameter(const std::string& param) const
186 {
187  checkUnique(param);
188  // if not found, peek only
189  bool peek_only = tree_->find(param) == tree_->not_found();
190  markVisited(param, Attr::TAG, peek_only);
191 }
192 
193 void ConfigTree::ignoreConfigAttribute(const std::string& attr) const
194 {
195  checkUniqueAttr(attr);
196 
197  // Exercise: Guess what not! (hint: if not found, peek only)
198  // Btw. (not a hint) tree_->find() does not seem to work here.
199  bool peek_only = !tree_->get_child_optional("<xmlattr>." + attr);
200 
201  markVisited(attr, Attr::ATTR, peek_only);
202 }
203 
204 void ConfigTree::ignoreConfigParameterAll(const std::string& param) const
205 {
206  checkUnique(param);
207  auto& ct = markVisited(param, Attr::TAG, true);
208 
209  auto p = tree_->equal_range(param);
210  for (auto it = p.first; it != p.second; ++it)
211  {
212  ++ct.count;
213  }
214 }
215 
216 void ConfigTree::error(const std::string& message) const
217 {
218  onerror_(filename_, path_, message);
219  OGS_FATAL(
220  "ConfigTree: The error handler does not break out of the normal "
221  "control flow.");
222 }
223 
224 void ConfigTree::warning(const std::string& message) const
225 {
226  onwarning_(filename_, path_, message);
227 }
228 
229 void ConfigTree::onerror(const std::string& filename, const std::string& path,
230  const std::string& message)
231 {
232  OGS_FATAL("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
233  message);
234 }
235 
236 void ConfigTree::onwarning(const std::string& filename, const std::string& path,
237  const std::string& message)
238 {
239  WARN("ConfigTree: In file `{:s}' at path <{:s}>: {:s}", filename, path,
240  message);
241 }
242 
244 {
246  {
247  return;
248  }
249 
250  ERR("ConfigTree: There have been errors when parsing the configuration "
251  "file(s):");
252 
253  for (auto const& msg : configtree_destructor_error_messages)
254  {
255  ERR("{:s}", msg);
256  }
257 
258  OGS_FATAL("There have been errors when parsing the configuration file(s).");
259 }
260 
261 std::string ConfigTree::shortString(const std::string& s)
262 {
263  const std::size_t maxlen = 100;
264 
265  if (s.size() < maxlen)
266  {
267  return s;
268  }
269 
270  return s.substr(0, maxlen - 3) + "...";
271 }
272 
273 void ConfigTree::checkKeyname(std::string const& key) const
274 {
275  if (key.empty())
276  {
277  error("Search for empty key.");
278  }
279  else if (key_chars_start.find(key.front()) == std::string::npos)
280  {
281  error("Key <" + key + "> starts with an illegal character.");
282  }
283  else if (key.find_first_not_of(key_chars, 1) != std::string::npos)
284  {
285  error("Key <" + key + "> contains illegal characters.");
286  }
287  else if (key.find("__") != std::string::npos)
288  {
289  // This is illegal because we use parameter names to generate doxygen
290  // page names. Thereby "__" acts as a separator character. Choosing
291  // other separators is not possible because of observed limitations
292  // for valid doxygen page names.
293  error("Key <" + key + "> contains double underscore.");
294  }
295 }
296 
297 std::string ConfigTree::joinPaths(const std::string& p1,
298  const std::string& p2) const
299 {
300  if (p2.empty())
301  {
302  error("Second path to be joined is empty.");
303  }
304 
305  if (p1.empty())
306  {
307  return p2;
308  }
309 
310  return p1 + pathseparator + p2;
311 }
312 
313 void ConfigTree::checkUnique(const std::string& key) const
314 {
315  checkKeyname(key);
316 
317  if (visited_params_.find({Attr::TAG, key}) != visited_params_.end())
318  {
319  error("Key <" + key + "> has already been processed.");
320  }
321 }
322 
323 void ConfigTree::checkUniqueAttr(const std::string& attr) const
324 {
325  // Workaround for handling attributes with xml namespaces and uppercase
326  // letters.
327  if (attr.find(':') != std::string::npos)
328  {
329  auto pos = decltype(std::string::npos){0};
330 
331  // Replace colon and uppercase letters with an allowed character 'a'.
332  // That means, attributes containing a colon are also allowed to contain
333  // uppercase letters.
334  auto attr2 = attr;
335  do
336  {
337  pos = attr2.find_first_of(":ABCDEFGHIJKLMNOPQRSTUVWXYZ", pos);
338  if (pos != std::string::npos)
339  {
340  attr2[pos] = 'a';
341  }
342  } while (pos != std::string::npos);
343 
344  checkKeyname(attr2);
345  }
346  else
347  {
348  checkKeyname(attr);
349  }
350 
351  if (visited_params_.find({Attr::ATTR, attr}) != visited_params_.end())
352  {
353  error("Attribute '" + attr + "' has already been processed.");
354  }
355 }
356 
358  Attr const is_attr,
359  bool const peek_only) const
360 {
361  return markVisited<ConfigTree>(key, is_attr, peek_only);
362 }
363 
365  std::string const& key) const
366 {
367  auto const type = std::type_index(typeid(nullptr));
368 
369  auto p = visited_params_.emplace(std::make_pair(is_attr, key),
370  CountType{-1, type});
371 
372  if (!p.second)
373  { // no insertion happened
374  auto& v = p.first->second;
375  --v.count;
376  }
377 }
378 
380 {
381  auto const& tree = *tree_;
382  if (tree.begin() == tree.end())
383  {
384  return false; // no children
385  }
386  if (tree.front().first == "<xmlattr>" && (++tree.begin()) == tree.end())
387  {
388  return false; // only attributes
389  }
390 
391  return true;
392 }
393 
395 {
396  if (!tree_)
397  {
398  return;
399  }
400 
401  // Note: due to a limitation in boost::property_tree it is not possible
402  // to discriminate between <tag></tag> and <tag/> in the input file.
403  // In both cases data() will be empty.
404  if ((!have_read_data_) && !tree_->data().empty())
405  {
406  warning("The immediate data `" + shortString(tree_->data()) +
407  "' of this tag has not been read.");
408  }
409 
410  // iterate over children
411  for (auto const& p : *tree_)
412  {
413  if (p.first != "<xmlattr>")
414  { // attributes are handled below
416  }
417  }
418 
419  // iterate over attributes
420  if (auto attrs = tree_->get_child_optional("<xmlattr>"))
421  {
422  for (auto const& p : *attrs)
423  {
425  }
426  }
427 
428  for (auto const& p : visited_params_)
429  {
430  auto const& tag = p.first.second;
431  auto const& count = p.second.count;
432 
433  switch (p.first.first)
434  {
435  case Attr::ATTR:
436  if (count > 0)
437  {
438  warning("XML attribute '" + tag + "' has been read " +
439  std::to_string(count) +
440  " time(s) more than it was present in the "
441  "configuration tree.");
442  }
443  else if (count < 0)
444  {
445  warning("XML attribute '" + tag + "' has been read " +
446  std::to_string(-count) +
447  " time(s) less than it was present in the "
448  "configuration tree.");
449  }
450  break;
451  case Attr::TAG:
452  if (count > 0)
453  {
454  warning("Key <" + tag + "> has been read " +
455  std::to_string(count) +
456  " time(s) more than it was present in the "
457  "configuration tree.");
458  }
459  else if (count < 0)
460  {
461  warning("Key <" + tag + "> has been read " +
462  std::to_string(-count) +
463  " time(s) less than it was present in the "
464  "configuration tree.");
465  }
466  }
467  }
468 
469  // The following invalidates this instance, s.t. it can not be read from it
470  // anymore, but it also prevents double-checking.
471  tree_ = nullptr;
472 }
473 
475 {
476  conf.checkAndInvalidate();
477 }
478 
480 {
481  if (conf)
482  {
483  conf->checkAndInvalidate();
484  }
485 }
486 
487 void checkAndInvalidate(std::unique_ptr<ConfigTree> const& conf)
488 {
489  if (conf)
490  {
491  conf->checkAndInvalidate();
492  }
493 }
494 
495 } // namespace BaseLib
static std::forward_list< std::string > configtree_destructor_error_messages
Definition: ConfigTree.cpp:26
#define OGS_FATAL(...)
Definition: Error.h:26
void ERR(fmt::format_string< Args... > fmt, Args &&... args)
Definition: Logging.h:44
void WARN(fmt::format_string< Args... > fmt, Args &&... args)
Definition: Logging.h:39
void markVisitedDecrement(Attr const is_attr, std::string const &key) const
Definition: ConfigTree.cpp:364
void ignoreConfigParameter(std::string const &param) const
Definition: ConfigTree.cpp:185
static void onerror(std::string const &filename, std::string const &path, std::string const &message)
Definition: ConfigTree.cpp:229
static void assertNoSwallowedErrors()
Asserts that there have not been any errors reported in the destructor.
Definition: ConfigTree.cpp:243
Callback onwarning_
Custom warning callback.
Definition: ConfigTree.h:673
static const char pathseparator
Character separating two path components.
Definition: ConfigTree.h:676
void checkUniqueAttr(std::string const &attr) const
Asserts that the attribute attr has not been read yet.
Definition: ConfigTree.cpp:323
std::map< KeyType, CountType > visited_params_
Definition: ConfigTree.h:666
static std::string shortString(std::string const &s)
returns a short string at suitable for error/warning messages
Definition: ConfigTree.cpp:261
std::shared_ptr< PTree const > top_level_tree_
Definition: ConfigTree.h:645
void error(std::string const &message) const
Definition: ConfigTree.cpp:216
std::optional< ConfigTree > getConfigSubtreeOptional(std::string const &root) const
Definition: ConfigTree.cpp:159
std::optional< T > getConfigParameterOptional(std::string const &param) const
void ignoreConfigAttribute(std::string const &attr) const
Definition: ConfigTree.cpp:193
void checkUnique(std::string const &key) const
Asserts that the key has not been read yet.
Definition: ConfigTree.cpp:313
T getConfigParameter(std::string const &param) const
Attr
Used to indicate if dealing with XML tags or XML attributes.
Definition: ConfigTree.h:572
Range< SubtreeIterator > getConfigSubtreeList(std::string const &root) const
Definition: ConfigTree.cpp:173
static const std::string key_chars_start
Set of allowed characters as the first letter of a key name.
Definition: ConfigTree.h:679
std::function< void(const std::string &filename, const std::string &path, const std::string &message)> Callback
Definition: ConfigTree.h:262
ConfigTree getConfigSubtree(std::string const &root) const
Definition: ConfigTree.cpp:150
std::string path_
A path printed in error/warning messages.
Definition: ConfigTree.h:651
CountType & markVisited(std::string const &key, Attr const is_attr, bool peek_only) const
void ignoreConfigParameterAll(std::string const &param) const
Definition: ConfigTree.cpp:204
Callback onerror_
Custom error callback.
Definition: ConfigTree.h:672
std::string joinPaths(std::string const &p1, std::string const &p2) const
Used to generate the path of a subtree.
Definition: ConfigTree.cpp:297
Range< ValueIterator< T > > getConfigParameterList(std::string const &param) const
ConfigTree & operator=(ConfigTree const &)=delete
copying is not compatible with the semantics of this class
void warning(std::string const &message) const
Definition: ConfigTree.cpp:224
bool hasChildren() const
Checks if this tree has any children.
Definition: ConfigTree.cpp:379
PTree const * tree_
The wrapped tree.
Definition: ConfigTree.h:648
ConfigTree(PTree &&top_level_tree, std::string filename, Callback error_cb, Callback warning_cb)
Definition: ConfigTree.cpp:34
boost::property_tree::ptree PTree
The tree being wrapped by this class.
Definition: ConfigTree.h:251
std::string filename_
The path of the file from which this tree has been read.
Definition: ConfigTree.h:654
void checkKeyname(std::string const &key) const
Checks if key complies with the rules [a-z0-9_].
Definition: ConfigTree.cpp:273
static void onwarning(std::string const &filename, std::string const &path, std::string const &message)
Definition: ConfigTree.cpp:236
static const std::string key_chars
Set of allowed characters in a key name.
Definition: ConfigTree.h:682
Wraps a pair of iterators for use as a range in range-based for-loops.
void checkAndInvalidate(ConfigTree &conf)
This is an overloaded member function, provided for convenience. It differs from the above function o...
Definition: ConfigTree.cpp:474
std::string joinPaths(std::string const &pathA, std::string const &pathB)
Definition: FileTools.cpp:213
static const double s
static const double v
static const double t