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