source: finroc_plugins_parameters/tConfigFile.cpp @ 85:8938c2bb579a

17.03
Last change on this file since 85:8938c2bb579a was 85:8938c2bb579a, checked in by Max Reichardt <mreichardt@…>, 13 months ago

Adds concept of explicitly overriding values in config files: values and nodes can be marked with 'override="true"'. This will remove warnings w.r.t. multiple entries with the same name - and select the last entry marked with 'override'.

File size: 18.7 KB
Line 
1//
2// You received this file as part of Finroc
3// A framework for intelligent robot control
4//
5// Copyright (C) Finroc GbR (finroc.org)
6//
7// This program is free software; you can redistribute it and/or modify
8// it under the terms of the GNU General Public License as published by
9// the Free Software Foundation; either version 2 of the License, or
10// (at your option) any later version.
11//
12// This program is distributed in the hope that it will be useful,
13// but WITHOUT ANY WARRANTY; without even the implied warranty of
14// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15// GNU General Public License for more details.
16//
17// You should have received a copy of the GNU General Public License along
18// with this program; if not, write to the Free Software Foundation, Inc.,
19// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
20//
21//----------------------------------------------------------------------
22/*!\file    plugins/parameters/tConfigFile.cpp
23 *
24 * \author  Max Reichardt
25 *
26 * \date    2012-11-28
27 *
28 */
29//----------------------------------------------------------------------
30#include "plugins/parameters/tConfigFile.h"
31
32//----------------------------------------------------------------------
33// External includes (system with <>, local with "")
34//----------------------------------------------------------------------
35#include "rrlib/rtti/rtti.h"
36#include "core/file_lookup.h"
37
38//----------------------------------------------------------------------
39// Internal includes with ""
40//----------------------------------------------------------------------
41#include "plugins/parameters/internal/tParameterInfo.h"
42
43//----------------------------------------------------------------------
44// Debugging
45//----------------------------------------------------------------------
46#include <cassert>
47
48//----------------------------------------------------------------------
49// Namespace usage
50//----------------------------------------------------------------------
51
52//----------------------------------------------------------------------
53// Namespace declaration
54//----------------------------------------------------------------------
55namespace finroc
56{
57namespace parameters
58{
59
60//----------------------------------------------------------------------
61// Forward declarations / typedefs / enums
62//----------------------------------------------------------------------
63#ifdef _LIB_RRLIB_XML_PRESENT_
64typedef rrlib::xml::tNode tXMLNode;
65
66//----------------------------------------------------------------------
67// Const values
68//----------------------------------------------------------------------
69/*! Separator entries are divided with */
70static const std::string cSEPARATOR("/");
71
72/*! Branch name in XML */
73static const std::string cXML_BRANCH_NAME("node");
74
75/*! Leaf name in XML */
76static const std::string cXML_LEAF_NAME("value");
77
78/*! Override attribute name in config files */
79static const std::string cXML_OVERRIDE_ATTRIBUTE_NAME("override");
80
81#endif
82
83/*! Initializes annotation type so that it can be transferred to finstruct */
84__attribute__((unused))
85static rrlib::rtti::tDataType<tConfigFile> cTYPE;
86
87//----------------------------------------------------------------------
88// Implementation
89//----------------------------------------------------------------------
90
91namespace
92{
93
94#ifdef _LIB_RRLIB_XML_PRESENT_
95
96rrlib::xml::tDocument LoadConfigFile(const std::string& file);
97
98bool ProcessIncludes(rrlib::xml::tNode& node, const std::string& source_file)
99{
100  if (node.Name() == "include")
101  {
102    if (!node.HasAttribute("file"))
103    {
104      FINROC_LOG_PRINT_STATIC(DEBUG_WARNING, "Found 'include' node without 'file' attribute in '", source_file, "'");
105    }
106    else
107    {
108      std::string file_name = node.GetStringAttribute("file");
109      try
110      {
111        rrlib::xml::tDocument insert = LoadConfigFile(file_name);
112        auto& parent = node.Parent();
113        for (auto it = insert.RootNode().ChildrenBegin(); it != insert.RootNode().ChildrenEnd(); ++it)
114        {
115          node.AddNextSibling(*it, true); // not using copy resulted in erroneous behavior
116        }
117        parent.RemoveChildNode(node);
118        return true;
119      }
120      catch (const std::exception& e)
121      {
122        FINROC_LOG_PRINT(ERROR, "Error including '", file_name, "' in config file: ", e);
123      }
124    }
125  }
126
127  long int index = 0;
128  for (auto it = node.ChildrenBegin(); it != node.ChildrenEnd();)
129  {
130    long int number_of_children = node.GetNumberOfChildren();
131    if (ProcessIncludes(*it, source_file))
132    {
133      // Iterator could be invalid -> reinitialize
134      long int size_difference = node.GetNumberOfChildren() - number_of_children;
135      assert(size_difference >= -1);
136      index += (size_difference + 1);
137      it = node.ChildrenBegin();
138      for (auto i = 0; i < index; i++)
139      {
140        ++it;
141      }
142    }
143    else
144    {
145      ++index;
146      ++it;
147    }
148  }
149  return false;
150}
151
152void ProcessIncludes(rrlib::xml::tDocument& document, const std::string& source_file)
153{
154  ProcessIncludes(document.RootNode(), source_file);
155}
156
157rrlib::xml::tDocument LoadConfigFile(const std::string& filename)
158{
159  rrlib::xml::tDocument result = core::GetFinrocXMLDocument(filename, false); // false = do not validate with dtd
160  ProcessIncludes(result, filename);
161  //FINROC_LOG_PRINT(DEBUG, result.RootNode().GetXMLDump(true));
162  return result;
163}
164
165bool OverrideContext(rrlib::xml::tNode& node, bool in_override_context)
166{
167  if (node.HasAttribute(cXML_OVERRIDE_ATTRIBUTE_NAME))
168  {
169    return node.GetBoolAttribute(cXML_OVERRIDE_ATTRIBUTE_NAME);
170  }
171  return in_override_context;
172}
173
174/*! Result of GetEntryImplementation function */
175struct GetEntryImplementationResult
176{
177  struct tEntry
178  {
179    rrlib::xml::tNode* node;
180    bool overrides;
181  };
182  tEntry first_entry, last_override_entry;
183  size_t entry_count = 0, override_entry_count = 0;
184};
185
186/*!
187 * Implementation of GetEntry() - called recursively
188 *
189 * \param result Object that will contain the result(s) as additional entry/entries after the call
190 * \param entry Entry
191 * \param node Current node
192 * \param entry_string_index Current index in entry string
193 * \param override_context Whether override="true" has been set in any parent now
194 */
195void GetEntryImplementation(GetEntryImplementationResult& result, const std::string& entry, rrlib::xml::tNode& node, size_t entry_string_index, bool override_context = false)
196{
197  if (entry_string_index >= entry.length())
198  {
199    return;
200  }
201
202  // Check for slash at beginning
203  if (entry[entry_string_index] == '/')
204  {
205    if (entry_string_index > 0)
206    {
207      FINROC_LOG_PRINT_STATIC(WARNING, "Entry '", entry, "' seems to be not clean (sequential slashes). Skipping one slash now, as this is typically intended. Please fix this!");
208    }
209    entry_string_index++;
210  }
211
212  // Search child nodes
213  for (rrlib::xml::tNode::iterator child = node.ChildrenBegin(); child != node.ChildrenEnd(); ++child)
214  {
215    if (child->Name() == cXML_BRANCH_NAME || child->Name() == cXML_LEAF_NAME)
216    {
217      std::string name_attribute;
218      try
219      {
220        name_attribute = child->GetStringAttribute("name");
221      }
222      catch (const std::exception& e)
223      {
224        FINROC_LOG_PRINT_STATIC(WARNING, "Encountered tree node without name");
225      }
226
227      try
228      {
229        if (entry.compare(entry_string_index, name_attribute.length(), name_attribute) == 0) // starts_with name attribute?
230        {
231          size_t new_entry_string_index = entry_string_index + name_attribute.length();
232          if (new_entry_string_index != entry.length())
233          {
234            if (entry[new_entry_string_index] == '/')
235            {
236              new_entry_string_index++;
237              GetEntryImplementation(result, entry, *child, new_entry_string_index, OverrideContext(*child, override_context));
238            }
239          }
240          else
241          {
242            // Entry found - include in result
243            GetEntryImplementationResult::tEntry new_entry = { &(*child), OverrideContext(*child, override_context) };
244            if (!result.entry_count)
245            {
246              result.first_entry = new_entry;
247              if (new_entry.overrides)
248              {
249                FINROC_LOG_PRINT_STATIC(WARNING, "First config entry with path '", entry, "' marked 'override', but does not override.");
250              }
251            }
252            if (new_entry.overrides)
253            {
254              result.last_override_entry = new_entry;
255            }
256            result.entry_count++;
257            result.override_entry_count += (new_entry.overrides ? 1 : 0);
258          }
259        }
260      }
261      catch (const std::exception& e)
262      {
263        FINROC_LOG_PRINT_STATIC(ERROR, "Error looking for config entry with path '", entry, "'. Reason: ", e.what());
264      }
265    }
266  }
267}
268
269rrlib::xml::tNode* ChooseNodeFromResult(GetEntryImplementationResult& result, const std::string& path)
270{
271  if (result.entry_count > 1)
272  {
273    assert(result.entry_count >= result.override_entry_count);
274    if (!result.override_entry_count)
275    {
276      FINROC_LOG_PRINT_STATIC(WARNING, "There are ", result.entry_count, " entries in config file with the path '", path, "'. Using the first one. Hint: Add attribute 'override=\"true\"' to nodes and values if the later entries are intended be used. This will also remove any warning messages.");
277    }
278    else if (result.entry_count - result.override_entry_count > 1)
279    {
280      FINROC_LOG_PRINT_STATIC(WARNING, "There are ", result.entry_count, " entries in config file with the path '", path, "'. Only ", result.override_entry_count, " are marked 'override'. Using the last one marked 'override'.");
281    }
282    return result.override_entry_count ? result.last_override_entry.node : result.first_entry.node;
283  }
284  return result.entry_count ? result.first_entry.node : nullptr;
285}
286
287#endif
288
289}
290
291tConfigFile::tConfigFile() :
292#ifdef _LIB_RRLIB_XML_PRESENT_
293  wrapped(),
294#endif
295  filename(),
296  active(true)
297{
298#ifdef _LIB_RRLIB_XML_PRESENT_
299  wrapped.AddRootNode(cXML_BRANCH_NAME);
300#endif
301}
302
303tConfigFile::tConfigFile(const std::string& filename, bool optional) :
304#ifdef _LIB_RRLIB_XML_PRESENT_
305  wrapped(),
306#endif
307  filename(filename),
308  active(true)
309{
310#ifdef _LIB_RRLIB_XML_PRESENT_
311  if (core::FinrocFileExists(filename))
312  {
313    try
314    {
315      wrapped = LoadConfigFile(filename);
316      return;
317    }
318    catch (const std::exception& e)
319    {
320      FINROC_LOG_PRINT(ERROR, e);
321    }
322  }
323  else if (!optional)
324  {
325    FINROC_LOG_PRINT(WARNING, "Specified config file not found: ", filename);
326  }
327  wrapped = rrlib::xml::tDocument();
328  wrapped.AddRootNode(cXML_BRANCH_NAME);
329#endif
330}
331
332void tConfigFile::Append(const std::string& filename)
333{
334#ifdef _LIB_RRLIB_XML_PRESENT_
335  if (core::FinrocFileExists(filename))
336  {
337    // merge entries into first document
338    auto document = LoadConfigFile(filename);
339    auto& root_node = document.RootNode();
340    for (auto it = root_node.ChildrenBegin(); it != root_node.ChildrenEnd(); ++it)
341    {
342      this->wrapped.RootNode().AddChildNode(*it, true); // not using copy resulted in erroneous behavior
343    }
344  }
345  else
346  {
347    throw std::runtime_error("Specified config file not found: " + filename);
348  }
349#endif
350}
351
352#ifdef _LIB_RRLIB_XML_PRESENT_
353rrlib::xml::tNode& tConfigFile::CreateEntry(const std::string& entry, bool leaf)
354{
355  if (!leaf)
356  {
357    GetEntryImplementationResult result;
358    GetEntryImplementation(result, entry, wrapped.RootNode(), 0);
359    rrlib::xml::tNode* result_node = ChooseNodeFromResult(result, entry);
360    if (result_node)
361    {
362      // do we want to warn if node is a leaf node? - I currently do not think so
363      return *result_node;
364    }
365  }
366
367  size_t slash_index = entry.rfind('/');
368  tXMLNode& parent = (slash_index == std::string::npos || slash_index == 0) ? wrapped.RootNode() : CreateEntry(entry.substr(0, slash_index), false);
369  tXMLNode& created = parent.AddChildNode(leaf ? cXML_LEAF_NAME : cXML_BRANCH_NAME);
370  created.SetAttribute("name", (slash_index == std::string::npos) ? entry : entry.substr(slash_index + 1));
371  return created;
372}
373#endif
374
375tConfigFile* tConfigFile::Find(const core::tFrameworkElement& element)
376{
377  tConfigFile* config_file = element.GetAnnotation<tConfigFile>();
378  if (config_file && config_file->active == true)
379  {
380    return config_file;
381  }
382  core::tFrameworkElement* parent = element.GetParent();
383  if (parent)
384  {
385    return Find(*parent);
386  }
387  return NULL;
388}
389
390#ifdef _LIB_RRLIB_XML_PRESENT_
391rrlib::xml::tNode& tConfigFile::GetEntry(const std::string& entry, bool create)
392{
393  GetEntryImplementationResult result;
394  GetEntryImplementation(result, entry, wrapped.RootNode(), 0);
395  rrlib::xml::tNode* result_node = ChooseNodeFromResult(result, entry);
396
397  if (!create)
398  {
399    if (!result_node)
400    {
401      throw std::runtime_error("Config node not found: " + entry);
402    }
403    if (result_node->Name() != cXML_LEAF_NAME)
404    {
405      throw std::runtime_error("Config node is no leaf: " + entry);
406    }
407    return *result_node;
408  }
409
410  // create node...
411  if (result.entry_count > 0)
412  {
413    // recreate existing node
414    std::string name = result_node->GetStringAttribute("name");
415    tXMLNode& parent = result_node->Parent();
416    tXMLNode& new_node = result_node->AddNextSibling(cXML_LEAF_NAME);
417    new_node.SetAttribute("name", name);
418    parent.RemoveChildNode(*result_node);
419    return new_node;
420  }
421  else
422  {
423    return CreateEntry(entry, true);
424  }
425}
426
427std::vector<std::string> tConfigFile::GetChildrenNames(const std::string &entry)
428{
429  GetEntryImplementationResult result;
430  GetEntryImplementation(result, entry, wrapped.RootNode(), 0);
431  rrlib::xml::tNode* result_node = ChooseNodeFromResult(result, entry);
432
433  if (!result_node)
434  {
435    throw std::runtime_error("Config node not found: " + entry);
436  }
437  if (result_node->Name() == cXML_LEAF_NAME)
438  {
439    throw std::runtime_error("Config node is a leaf: " + entry);
440  }
441
442  std::vector<std::string> children_names;
443  for (auto it = result_node->ChildrenBegin(); it != result_node->ChildrenEnd(); ++it)
444  {
445    children_names.push_back(it->GetStringAttribute("name"));
446  }
447  return children_names;
448}
449
450#endif
451
452std::string tConfigFile::GetStringEntry(const std::string& entry)
453{
454#ifdef _LIB_RRLIB_XML_PRESENT_
455  try
456  {
457    return GetEntry(entry).GetTextContent();
458  }
459  catch (const std::exception& e)
460  {
461    return "";
462  }
463#else
464  return "";
465#endif
466}
467
468bool tConfigFile::HasEntry(const std::string& entry)
469{
470#ifdef _LIB_RRLIB_XML_PRESENT_
471  // TODO: could be implemented more efficiently
472  try
473  {
474    GetEntry(entry);
475    return true;
476  }
477  catch (const std::exception& e)
478  {
479    return false;
480  }
481#else
482  return false;
483#endif
484}
485
486void tConfigFile::LoadParameterValues()
487{
488  LoadParameterValues(*GetAnnotated<core::tFrameworkElement>());
489}
490
491void tConfigFile::LoadParameterValues(core::tFrameworkElement& fe)
492{
493  rrlib::thread::tLock lock(fe.GetStructureMutex());  // nothing should change while we're doing this
494  for (auto it = fe.SubElementsBegin(true); it != fe.SubElementsEnd(); ++it)
495  {
496    if (it->IsPort() && it->IsReady() && Find(*it) == this)    // Does element belong to this configuration file?
497    {
498      internal::tParameterInfo* pi = it->GetAnnotation<internal::tParameterInfo>();
499      if (pi)
500      {
501        try
502        {
503          pi->LoadValue();
504        }
505        catch (const std::exception& e)
506        {
507          FINROC_LOG_PRINT_STATIC(ERROR, e);
508        }
509      }
510    }
511  }
512}
513
514void tConfigFile::Reload()
515{
516  if (core::FinrocFileExists(filename))
517  {
518    try
519    {
520      wrapped = LoadConfigFile(filename);
521      return;
522    }
523    catch (const std::exception& e)
524    {
525      FINROC_LOG_PRINT(ERROR, e);
526    }
527  }
528}
529
530void tConfigFile::SaveFile(const std::string& new_filename)
531{
532#ifdef _LIB_RRLIB_XML_PRESENT_
533  // first: update tree
534  core::tFrameworkElement* ann = GetAnnotated<core::tFrameworkElement>();
535  if (ann)
536  {
537    rrlib::thread::tLock lock(ann->GetStructureMutex()); // nothing should change while we're doing this
538    for (auto it = ann->SubElementsBegin(true); it != ann->SubElementsEnd(); ++it)
539    {
540      if (it->IsPort() && it->IsReady() && Find(*it) == this)    // Does element belong to this configuration file?
541      {
542        internal::tParameterInfo* pi = it->GetAnnotation<internal::tParameterInfo>();
543        if (pi)
544        {
545          try
546          {
547            pi->SaveValue();
548          }
549          catch (const std::exception& e)
550          {
551            FINROC_LOG_PRINT_STATIC(ERROR, e);
552          }
553        }
554      }
555    }
556  }
557
558  try
559  {
560    if (new_filename.length() > 0)
561    {
562      this->filename = new_filename;
563    }
564    std::string save_to = core::GetFinrocFileToSaveTo(this->filename);
565    if (save_to.length() == 0)
566    {
567      std::string save_to_alt = save_to;
568      std::replace(save_to_alt.begin(), save_to_alt.end(), '/', '_'); // Replace '/' characters with '_'
569      save_to_alt = core::GetFinrocFileToSaveTo(save_to_alt);
570      FINROC_LOG_PRINT(ERROR, "There does not seem to be any suitable location for: '", this->filename, "' . For now, using '", save_to_alt, "'.");
571      save_to = save_to_alt;
572    }
573
574    // write new tree to file
575    wrapped.WriteToFile(save_to);
576  }
577  catch (const std::exception& e)
578  {
579    FINROC_LOG_PRINT(ERROR, e);
580  }
581#endif
582}
583
584rrlib::serialization::tOutputStream& operator << (rrlib::serialization::tOutputStream& stream, const tConfigFile& config_file)
585{
586#ifdef _LIB_RRLIB_XML_PRESENT_
587  stream.WriteBoolean(config_file.IsActive());
588  stream.WriteString(config_file.GetFilename());
589
590  try
591  {
592    stream.WriteString(config_file.wrapped.RootNode().GetXMLDump());
593  }
594  catch (const std::exception& e)
595  {
596    FINROC_LOG_PRINT_STATIC(ERROR, e); // Should never occur
597  }
598#endif
599  return stream;
600}
601
602rrlib::serialization::tInputStream& operator >> (rrlib::serialization::tInputStream& stream, tConfigFile& config_file)
603{
604#ifdef _LIB_RRLIB_XML_PRESENT_
605  config_file.active = stream.ReadBoolean();
606  std::string file = stream.ReadString();
607  std::string content = stream.ReadString();
608
609  if (config_file.active && file.length() > 0 && content.length() == 0 && (file != config_file.filename))
610  {
611    // load file
612    if (core::FinrocFileExists(file))
613    {
614      try
615      {
616        config_file.wrapped = LoadConfigFile(file);
617      }
618      catch (const std::exception& e)
619      {
620        FINROC_LOG_PRINT_STATIC(ERROR, e);
621        config_file.wrapped = rrlib::xml::tDocument();
622        try
623        {
624          config_file.wrapped.AddRootNode(cXML_BRANCH_NAME);
625        }
626        catch (const std::exception& e1)
627        {
628          FINROC_LOG_PRINT_STATIC(ERROR, e1);
629        }
630      }
631    }
632    config_file.filename = file;
633  }
634  else if (config_file.active && content.length() > 0)
635  {
636    if (file.length() > 0)
637    {
638      config_file.filename = file;
639    }
640
641    try
642    {
643      config_file.wrapped = rrlib::xml::tDocument(content.c_str(), content.length() + 1);
644    }
645    catch (const std::exception& e)
646    {
647      FINROC_LOG_PRINT_STATIC(ERROR, e);
648    }
649  }
650#endif
651  return stream;
652}
653
654//----------------------------------------------------------------------
655// End of namespace declaration
656//----------------------------------------------------------------------
657}
658}
Note: See TracBrowser for help on using the repository browser.