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