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