otsdaq  v2_05_02_indev
ConfigurationTree.cc
1 #include "otsdaq/ConfigurationInterface/ConfigurationTree.h"
2 
3 #include <typeinfo>
4 
5 #include "otsdaq/ConfigurationInterface/ConfigurationManager.h"
6 #include "otsdaq/Macros/StringMacros.h"
7 #include "otsdaq/TableCore/TableBase.h"
8 
9 using namespace ots;
10 
11 #undef __MF_SUBJECT__
12 #define __MF_SUBJECT__ "ConfigurationTree"
13 
14 const std::string ConfigurationTree::DISCONNECTED_VALUE = "X";
15 const std::string ConfigurationTree::VALUE_TYPE_DISCONNECTED = "Disconnected";
16 const std::string ConfigurationTree::VALUE_TYPE_NODE = "Node";
17 const std::string ConfigurationTree::ROOT_NAME = "/";
18 
19 //==============================================================================
20 ConfigurationTree::ConfigurationTree()
21  : configMgr_(0)
22  , table_(0)
23  , groupId_("")
24  , linkParentConfig_(0)
25  , linkColName_("")
26  , linkColValue_("")
27  , linkBackRow_(0)
28  , linkBackCol_(0)
29  , disconnectedTargetName_("")
30  , disconnectedLinkID_("")
31  , childLinkIndex_("")
32  , row_(0)
33  , col_(0)
34  , tableView_(0)
35 {
36  //__COUT__ << __E__;
37  //__COUT__ << "EMPTY CONSTRUCTOR ConfigManager: " << configMgr_ << " configuration: "
38  //<< table_ << __E__;
39 } // end empty constructor
40 
41 //==============================================================================
42 ConfigurationTree::ConfigurationTree(const ConfigurationManager* const& configMgr, const TableBase* const& config)
43  : ConfigurationTree(configMgr,
44  config,
45  "" /*groupId_*/,
46  0 /*linkParentConfig_*/,
47  "" /*linkColName_*/,
48  "" /*linkColValue_*/,
49  TableView::INVALID /*linkBackRow_*/,
50  TableView::INVALID /*linkBackCol_*/,
51  "" /*disconnectedTargetName_*/,
52  "" /*disconnectedLinkID_*/,
53  "" /*childLinkIndex_*/,
54  TableView::INVALID /*row_*/,
55  TableView::INVALID /*col_*/)
56 {
57  //__COUT__ << __E__;
58  //__COUT__ << "SHORT CONTRUCTOR ConfigManager: " << configMgr_ << " configuration: "
59  //<< table_ << __E__;
60 } // end short constructor
61 
62 //==============================================================================
63 ConfigurationTree::ConfigurationTree(const ConfigurationManager* const& configMgr,
64  const TableBase* const& config,
65  const std::string& groupId,
66  const TableBase* const& linkParentConfig,
67  const std::string& linkColName,
68  const std::string& linkColValue,
69  const unsigned int linkBackRow,
70  const unsigned int linkBackCol,
71  const std::string& disconnectedTargetName,
72  const std::string& disconnectedLinkID,
73  const std::string& childLinkIndex,
74  const unsigned int row,
75  const unsigned int col)
76  : configMgr_(configMgr)
77  , table_(config)
78  , groupId_(groupId)
79  , linkParentConfig_(linkParentConfig)
80  , linkColName_(linkColName)
81  , linkColValue_(linkColValue)
82  , linkBackRow_(linkBackRow)
83  , linkBackCol_(linkBackCol)
84  , disconnectedTargetName_(disconnectedTargetName)
85  , disconnectedLinkID_(disconnectedLinkID)
86  , childLinkIndex_(childLinkIndex)
87  , row_(row)
88  , col_(col)
89  , tableView_(0)
90 {
91  //__COUT__ << __E__;
92  //__COUT__ << "FULL CONTRUCTOR ConfigManager: " << configMgr_ << " configuration: " <<
93  // table_ << __E__;
94  if(!configMgr_) // || !table_ || !tableView_)
95  {
96  __SS__ << "Invalid empty pointer given to tree!\n"
97  << "\n\tconfigMgr_=" << configMgr_ << "\n\tconfiguration_=" << table_ << "\n\tconfigView_=" << tableView_ << __E__;
98 
99  ss << nodeDump() << __E__;
100  __SS_THROW__;
101  }
102 
103  if(table_)
104  tableView_ = &(table_->getView());
105 
106  // verify UID column exists
107  if(tableView_ && tableView_->getColumnInfo(tableView_->getColUID()).getType() != TableViewColumnInfo::TYPE_UID)
108  {
109  __SS__ << "Missing UID column (must column of type " << TableViewColumnInfo::TYPE_UID << ") in config view : " << tableView_->getTableName() << __E__;
110 
111  ss << nodeDump() << __E__;
112  __SS_THROW__;
113  }
114 } // end full constructor
115 
116 //==============================================================================
117 // destructor
118 ConfigurationTree::~ConfigurationTree(void)
119 {
120  //__COUT__ << __E__;
121 } // end destructor
122 
123 //==============================================================================
124 // print
125 // print out tree from this node for desired depth
126 // depth of 0 means print out only this node's value
127 // depth of 1 means include this node's children's values, etc..
128 // depth of -1 means print full tree
129 void ConfigurationTree::print(const unsigned int& depth, std::ostream& out) const { recursivePrint(*this, depth, out, "\t"); } // end print()
130 
131 //==============================================================================
132 void ConfigurationTree::recursivePrint(const ConfigurationTree& t, unsigned int depth, std::ostream& out, std::string space)
133 {
134  if(t.isValueNode())
135  out << space << t.getValueName() << " :\t" << t.getValueAsString() << __E__;
136  else
137  {
138  if(t.isLinkNode())
139  {
140  out << space << t.getValueName();
141  if(t.isDisconnected())
142  {
143  out << " :\t" << t.getValueAsString() << __E__;
144  return;
145  }
146  out << " (" << (t.isGroupLinkNode() ? "Group" : "U") << "ID=" << t.getValueAsString() << ") : " << __E__;
147  }
148  else
149  out << space << t.getValueAsString() << " : " << __E__;
150 
151  // if depth>=1 print all children
152  // child.print(depth-1)
153  if(depth >= 1)
154  {
155  auto C = t.getChildren();
156  if(!C.empty())
157  out << space << "{" << __E__;
158  for(auto& c : C)
159  recursivePrint(c.second, depth - 1, out, space + " ");
160  if(!C.empty())
161  out << space << "}" << __E__;
162  }
163  }
164 } // end recursivePrint()
165 
166 //==============================================================================
167 std::string ConfigurationTree::handleValidateValueForColumn(const TableView* configView, std::string value, unsigned int col, ots::identity<std::string>) const
168 {
169  if(!configView)
170  {
171  __SS__ << "Null configView" << __E__;
172 
173  ss << nodeDump() << __E__;
174  __SS_THROW__;
175  }
176  __COUT__ << "handleValidateValueForColumn<string>" << __E__;
177  return configView->validateValueForColumn(value, col);
178 } // end std::string handleValidateValueForColumn()
179 
180 //==============================================================================
181 // getValue (only std::string value)
182 // special version of getValue for string type
183 // Note: necessary because types of std::basic_string<char> cause compiler problems if no
184 // string specific function
185 void ConfigurationTree::getValue(std::string& value) const
186 {
187  //__COUT__ << row_ << " " << col_ << " p: " << tableView_<< __E__;
188 
189  if(row_ != TableView::INVALID && col_ != TableView::INVALID) // this node is a value node
190  {
191  // attempt to interpret the value as a tree node path itself
192  try
193  {
194  ConfigurationTree valueAsTreeNode = getValueAsTreeNode();
195  // valueAsTreeNode.getValue<T>(value);
196  __COUT__ << "Success following path to tree node!" << __E__;
197  // value has been interpreted as a tree node value
198  // now verify result under the rules of this column
199  // if(typeid(std::string) == typeid(value))
200 
201  // Note: want to interpret table value as though it is in column of different
202  // table this allows a number to be read as a string, for example, without
203  // exceptions
204  value = tableView_->validateValueForColumn(valueAsTreeNode.getValueAsString(), col_);
205 
206  __COUT__ << "Successful value!" << __E__;
207 
208  // else
209  // value = tableView_->validateValueForColumn<T>(
210  // valueAsTreeNode.getValueAsString(),col_);
211 
212  return;
213  }
214  catch(...) // tree node path interpretation failed
215  {
216  //__COUT__ << "Invalid path, just returning normal value." << __E__;
217  }
218 
219  // else normal return
220  tableView_->getValue(value, row_, col_);
221  }
222  else if(row_ == TableView::INVALID && col_ == TableView::INVALID) // this node is table node maybe with groupId
223  {
224  if(isLinkNode() && isDisconnected())
225  value = (groupId_ == "") ? getValueName() : groupId_; // a disconnected link
226  // still knows its table
227  // name or groupId
228  else
229  value = (groupId_ == "") ? table_->getTableName() : groupId_;
230  }
231  else if(row_ == TableView::INVALID)
232  {
233  __SS__ << "Malformed ConfigurationTree" << __E__;
234  __SS_THROW__;
235  }
236  else if(col_ == TableView::INVALID) // this node is uid node
237  tableView_->getValue(value, row_, tableView_->getColUID());
238  else
239  {
240  __SS__ << "Impossible." << __E__;
241  __SS_THROW__;
242  }
243 } // end getValue()
244 
245 //==============================================================================
246 // getValue
247 // Only std::string value will work.
248 // If this is a value node, and not type string, configView->getValue should
249 // throw exception.
250 //
251 // NOTE: getValueAsString() method should be preferred if getting the Link UID
252 // because when disconnected will return "X". getValue() would return the
253 // column name of the link when disconnected.
254 //
256 // Note: if called without template, necessary because types of std::basic_string<char>
257 // cause compiler problems if no string specific function
258 std::string ConfigurationTree::getValue() const
259 {
260  std::string value;
261  ConfigurationTree::getValue(value);
262  return value;
263 } // end getValue()
264 //==============================================================================
265 // getValueWithDefault
266 // Only std::string value will work.
267 // If this is a value node, and not type string, configView->getValue should
268 // throw exception.
269 //
270 // NOTE: getValueAsString() method should be preferred if getting the Link UID
271 // because when disconnected will return "X". getValue() would return the
272 // column name of the link when disconnected.
273 //
275 // Note: if called without template, necessary because types of std::basic_string<char>
276 // cause compiler problems if no string specific function
277 std::string ConfigurationTree::getValueWithDefault(const std::string& defaultValue) const
278 {
279  if(isDefaultValue())
280  return defaultValue;
281  else
282  return ConfigurationTree::getValue();
283 } // end getValueWithDefault()
284 
285 //==============================================================================
286 // getValue (only ConfigurationTree::BitMap value)
287 // special version of getValue for string type
288 // Note: necessary because types of std::basic_string<char> cause compiler problems if no
289 // string specific function
290 void ConfigurationTree::getValueAsBitMap(ConfigurationTree::BitMap& bitmap) const
291 {
292  //__COUT__ << row_ << " " << col_ << " p: " << tableView_<< __E__;
293 
294  if(row_ != TableView::INVALID && col_ != TableView::INVALID) // this node is a value node
295  {
296  std::string bitmapString;
297  tableView_->getValue(bitmapString, row_, col_);
298 
299  __COUTV__(bitmapString);
300  if(bitmapString == TableViewColumnInfo::DATATYPE_STRING_DEFAULT)
301  {
302  bitmap.isDefault_ = true;
303  return;
304  }
305  else
306  bitmap.isDefault_ = false;
307 
308  // extract bit map
309  {
310  bitmap.bitmap_.clear();
311  int row = -1;
312  bool openRow = false;
313  unsigned int startInt = -1;
314  for(unsigned int i = 0; i < bitmapString.length(); i++)
315  {
316  __COUTV__(bitmapString[i]);
317  __COUTV__(row);
318  __COUTV__(openRow);
319  __COUTV__(startInt);
320  __COUTV__(i);
321 
322  if(!openRow) // need start of row
323  {
324  if(bitmapString[i] == '[')
325  { // open a new row
326  openRow = true;
327  ++row;
328  bitmap.bitmap_.push_back(std::vector<uint64_t>());
329  }
330  else if(bitmapString[i] == ']')
331  {
332  break; // ending bracket, done with string
333  }
334  else if(bitmapString[i] == ',') // end characters found not within
335  // row
336  {
337  __SS__ << "Too many ']' or ',' characters in bit map configuration" << __E__;
338 
339  ss << nodeDump() << __E__;
340  __SS_ONLY_THROW__;
341  }
342  }
343  else if(startInt == (unsigned int)-1) // need to find start of number
344  {
345  if(bitmapString[i] == ']') // found end of row, instead of start of
346  // number, assume row ended
347  {
348  openRow = false;
349  }
350  else if(bitmapString[i] >= '0' && bitmapString[i] <= '9') // found start of number
351  {
352  startInt = i;
353  }
354  else if(bitmapString[i] == ',') // comma found without number
355  {
356  __SS__ << "Too many ',' characters in bit map configuration" << __E__;
357 
358  ss << nodeDump() << __E__;
359  __SS_ONLY_THROW__;
360  }
361  }
362  else
363  {
364  // looking for end of number
365 
366  if(bitmapString[i] == ']') // found end of row, assume row and number ended
367  {
368  openRow = false;
369  bitmap.bitmap_[row].push_back(strtoul(bitmapString.substr(startInt, i - startInt).c_str(), 0, 0));
370  startInt = -1;
371  }
372  else if(bitmapString[i] == ',') // comma found, assume end of number
373  {
374  bitmap.bitmap_[row].push_back(strtoul(bitmapString.substr(startInt, i - startInt).c_str(), 0, 0));
375  startInt = -1;
376  }
377  }
378  }
379 
380  for(unsigned int r = 0; r < bitmap.bitmap_.size(); ++r)
381  {
382  for(unsigned int c = 0; c < bitmap.bitmap_[r].size(); ++c)
383  {
384  __COUT__ << r << "," << c << " = " << bitmap.bitmap_[r][c] << __E__;
385  }
386  __COUT__ << "================" << __E__;
387  }
388  }
389  }
390  else
391  {
392  __SS__ << "Requesting getValue must be on a value node." << __E__;
393 
394  ss << nodeDump() << __E__;
395  __SS_THROW__;
396  }
397 } // end getValueAsBitMap()
398 
399 //==============================================================================
400 // getValue
401 //
402 // special version of getValue for ConfigurationTree::BitMap type
403 ConfigurationTree::BitMap ConfigurationTree::getValueAsBitMap() const
404 {
406  ConfigurationTree::getValueAsBitMap(value);
407  return value;
408 } // end getValueAsBitMap()
409 
410 //==============================================================================
411 // getEscapedValue
412 // Only works if a value node, other exception thrown
413 std::string ConfigurationTree::getEscapedValue() const
414 {
415  if(row_ != TableView::INVALID && col_ != TableView::INVALID) // this node is a value node
416  return tableView_->getEscapedValueAsString(row_, col_);
417 
418  __SS__ << "Can not get escaped value except from a value node!"
419  << " This node is type '" << getNodeType() << "." << __E__;
420 
421  ss << nodeDump() << __E__;
422  __SS_THROW__;
423 } // end getEscapedValue()
424 
425 //==============================================================================
426 // getTableName
427 const std::string& ConfigurationTree::getTableName(void) const
428 {
429  if(!table_)
430  {
431  __SS__ << "Can not get configuration name of node with no configuration pointer! "
432  << "Is there a broken link? " << __E__;
433  if(linkParentConfig_)
434  {
435  ss << "Error occurred traversing from " << linkParentConfig_->getTableName() << " UID '"
436  << linkParentConfig_->getView().getValueAsString(linkBackRow_, linkParentConfig_->getView().getColUID()) << "' at row " << linkBackRow_
437  << " col '" << linkParentConfig_->getView().getColumnInfo(linkBackCol_).getName() << ".'" << __E__;
438 
439  ss << StringMacros::stackTrace() << __E__;
440  }
441 
442  __SS_ONLY_THROW__;
443  }
444  return table_->getTableName();
445 } // end getTableName()
446 
447 //==============================================================================
448 // getNodeRow
449 const unsigned int& ConfigurationTree::getNodeRow(void) const
450 {
451  if(isUIDNode() || isValueNode())
452  return row_;
453 
454  __SS__ << "Can only get row from a UID or value node!" << __E__;
455  if(linkParentConfig_)
456  {
457  ss << "Error occurred traversing from " << linkParentConfig_->getTableName() << " UID '"
458  << linkParentConfig_->getView().getValueAsString(linkBackRow_, linkParentConfig_->getView().getColUID()) << "' at row " << linkBackRow_ << " col '"
459  << linkParentConfig_->getView().getColumnInfo(linkBackCol_).getName() << ".'" << __E__;
460 
461  ss << StringMacros::stackTrace() << __E__;
462  }
463 
464  __SS_ONLY_THROW__;
465 
466 } // end getNodeRow()
467 
468 //==============================================================================
469 // getFieldTableName
470 // returns the configuration name for the node's field.
471 // Note: for link nodes versus value nodes this has different functionality than
472 // getTableName()
473 const std::string& ConfigurationTree::getFieldTableName(void) const
474 {
475  // if link node, need config name from parent
476  if(isLinkNode())
477  {
478  if(!linkParentConfig_)
479  {
480  __SS__ << "Can not get configuration name of link node field with no parent "
481  "configuration pointer!"
482  << __E__;
483  ss << nodeDump() << __E__;
484  __SS_ONLY_THROW__;
485  }
486  return linkParentConfig_->getTableName();
487  }
488  else
489  return getTableName();
490 } // end getFieldTableName()
491 
492 //==============================================================================
493 // getDisconnectedTableName
494 const std::string& ConfigurationTree::getDisconnectedTableName(void) const
495 {
496  if(isLinkNode() && isDisconnected())
497  return disconnectedTargetName_;
498 
499  __SS__ << "Can not get disconnected target name of node unless it is a disconnected "
500  "link node!"
501  << __E__;
502 
503  ss << nodeDump() << __E__;
504  __SS_ONLY_THROW__;
505 } // end getDisconnectedTableName()
506 
507 //==============================================================================
508 // getDisconnectedLinkID
509 const std::string& ConfigurationTree::getDisconnectedLinkID(void) const
510 {
511  if(isLinkNode() && isDisconnected())
512  return disconnectedLinkID_;
513 
514  __SS__ << "Can not get disconnected target name of node unless it is a disconnected "
515  "link node!"
516  << __E__;
517 
518  ss << nodeDump() << __E__;
519  __SS_ONLY_THROW__;
520 } // end getDisconnectedLinkID()
521 
522 //==============================================================================
523 // getTableVersion
524 const TableVersion& ConfigurationTree::getTableVersion(void) const
525 {
526  if(!tableView_)
527  {
528  __SS__ << "Can not get configuration version of node with no config view pointer!" << __E__;
529 
530  ss << nodeDump() << __E__;
531  __SS_ONLY_THROW__;
532  }
533  return tableView_->getVersion();
534 } // end getTableVersion()
535 
536 //==============================================================================
537 // getTableCreationTime
538 const time_t& ConfigurationTree::getTableCreationTime(void) const
539 {
540  if(!tableView_)
541  {
542  __SS__ << "Can not get configuration creation time of node with no config view "
543  "pointer!"
544  << __E__;
545 
546  ss << nodeDump() << __E__;
547  __SS_ONLY_THROW__;
548  }
549  return tableView_->getCreationTime();
550 } // end getTableCreationTime()
551 
552 //==============================================================================
553 // getSetOfGroupIDs
554 // returns set of group IDs if groupID value node
555 std::set<std::string> ConfigurationTree::getSetOfGroupIDs(void) const
556 {
557  if(!isGroupIDNode())
558  {
559  __SS__ << "Can not get set of group IDs of node with value type of '" << getNodeType() << ".' Node must be a GroupID node." << __E__;
560 
561  ss << nodeDump() << __E__;
562  __SS_ONLY_THROW__;
563  }
564 
565  return tableView_->getSetOfGroupIDs(col_, row_);
566 
567 } // end getSetOfGroupIDs()
568 
569 //==============================================================================
570 // getFixedChoices
571 // returns vector of default + data choices
572 // Used as choices for tree-view, for example.
573 std::vector<std::string> ConfigurationTree::getFixedChoices(void) const
574 {
575  if(getValueType() != TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA && getValueType() != TableViewColumnInfo::TYPE_BITMAP_DATA && !isLinkNode())
576  {
577  __SS__ << "Can not get fixed choices of node with value type of '" << getValueType() << ".' Node must be a link or a value node with type '"
578  << TableViewColumnInfo::TYPE_BITMAP_DATA << "' or '" << TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA << ".'" << __E__;
579 
580  ss << nodeDump() << __E__;
581  __SS_ONLY_THROW__;
582  }
583 
584  std::vector<std::string> retVec;
585 
586  if(isLinkNode())
587  {
588  if(!linkParentConfig_)
589  {
590  __SS__ << "Can not get fixed choices of node with no parent config view pointer!" << __E__;
591 
592  ss << nodeDump() << __E__;
593  __SS_ONLY_THROW__;
594  }
595 
596  //__COUT__ << getChildLinkIndex() << __E__;
597  //__COUT__ << linkColName_ << __E__;
598 
599  // for links, col_ = -1, column c needs to change (to ChildLink column of pair)
600  // get column from parent config pointer
601 
602  const TableView* parentView = &(linkParentConfig_->getView());
603  int c = parentView->findCol(linkColName_);
604 
605  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
606  bool isGroupLink;
607  parentView->getChildLink(c, isGroupLink, linkPair);
608  c = linkPair.first;
609 
610  std::vector<std::string> choices = parentView->getColumnInfo(c).getDataChoices();
611  for(const auto& choice : choices)
612  retVec.push_back(choice);
613 
614  return retVec;
615  }
616 
617  if(!tableView_)
618  {
619  __SS__ << "Can not get fixed choices of node with no config view pointer!" << __E__;
620 
621  ss << nodeDump() << __E__;
622  __SS_ONLY_THROW__;
623  }
624 
625  // return vector of default + data choices
626  retVec.push_back(tableView_->getColumnInfo(col_).getDefaultValue());
627  std::vector<std::string> choices = tableView_->getColumnInfo(col_).getDataChoices();
628  for(const auto& choice : choices)
629  retVec.push_back(choice);
630 
631  return retVec;
632 } // end getFixedChoices()
633 
634 //==============================================================================
635 // getValueAsString
636 // NOTE: getValueAsString() method should be preferred if getting the Link UID
637 // because when disconnected will return "X". getValue() would return the
638 // column name of the link when disconnected.
639 //
640 // returnLinkTableValue returns the value in the source table as though link was
641 // not followed to destination table.
642 const std::string& ConfigurationTree::getValueAsString(bool returnLinkTableValue) const
643 {
644  //__COUTV__(col_);__COUTV__(row_);__COUTV__(table_);__COUTV__(tableView_);
645 
646  if(isLinkNode())
647  {
648  if(returnLinkTableValue)
649  return linkColValue_;
650  else if(isDisconnected())
651  return ConfigurationTree::DISCONNECTED_VALUE;
652  else if(row_ == TableView::INVALID && col_ == TableView::INVALID) // this link is groupId node
653  return (groupId_ == "") ? table_->getTableName() : groupId_;
654  else if(col_ == TableView::INVALID) // this link is uid node
655  return tableView_->getDataView()[row_][tableView_->getColUID()];
656  else
657  {
658  __SS__ << "Impossible Link." << __E__;
659 
660  ss << nodeDump() << __E__;
661  __SS_THROW__;
662  }
663  }
664  else if(row_ != TableView::INVALID && col_ != TableView::INVALID) // this node is a value node
665  return tableView_->getDataView()[row_][col_];
666  else if(row_ == TableView::INVALID && col_ == TableView::INVALID) // this node is table node maybe with groupId
667  {
668  // if root node, then no table defined
669  if(isRootNode())
670  return ConfigurationTree::ROOT_NAME;
671 
672  return (groupId_ == "") ? table_->getTableName() : groupId_;
673  }
674  else if(row_ == TableView::INVALID)
675  {
676  __SS__ << "Malformed ConfigurationTree" << __E__;
677 
678  ss << nodeDump() << __E__;
679  __SS_THROW__;
680  }
681  else if(col_ == TableView::INVALID) // this node is uid node
682  return tableView_->getDataView()[row_][tableView_->getColUID()];
683  else
684  {
685  __SS__ << "Impossible." << __E__;
686 
687  ss << nodeDump() << __E__;
688  __SS_THROW__;
689  }
690 } // end getValueAsString()
691 
692 //==============================================================================
693 // getUIDAsString
694 // returns UID associated with current value node or UID-Link node
695 //
696 const std::string& ConfigurationTree::getUIDAsString(void) const
697 {
698  if(isValueNode() || isUIDLinkNode() || isUIDNode())
699  return tableView_->getDataView()[row_][tableView_->getColUID()];
700 
701  {
702  __SS__ << "Can not get UID of node with type '" << getNodeType() << ".' Node type must be '" << ConfigurationTree::NODE_TYPE_VALUE << "' or '"
703  << ConfigurationTree::NODE_TYPE_UID_LINK << ".'" << __E__;
704 
705  ss << nodeDump() << __E__;
706  __SS_ONLY_THROW__;
707  }
708 } // end getUIDAsString()
709 
710 //==============================================================================
711 // getValueDataType
712 // e.g. used to determine if node is type NUMBER
713 const std::string& ConfigurationTree::getValueDataType(void) const
714 {
715  if(isValueNode())
716  return tableView_->getColumnInfo(col_).getDataType();
717  else // must be std::string
718  return TableViewColumnInfo::DATATYPE_STRING;
719 } // end getValueDataType()
720 
721 //==============================================================================
722 // isDefaultValue
723 // returns true if is a value node and value is the default for the type
724 bool ConfigurationTree::isDefaultValue(void) const
725 {
726  if(!isValueNode())
727  return false;
728 
729  if(getValueDataType() == TableViewColumnInfo::DATATYPE_STRING)
730  {
731  if(getValueType() == TableViewColumnInfo::TYPE_ON_OFF || getValueType() == TableViewColumnInfo::TYPE_TRUE_FALSE ||
732  getValueType() == TableViewColumnInfo::TYPE_YES_NO)
733  return getValueAsString() == TableViewColumnInfo::DATATYPE_BOOL_DEFAULT; // default to OFF, NO,
734  // FALSE
735  else if(getValueType() == TableViewColumnInfo::TYPE_COMMENT)
736  return getValueAsString() == TableViewColumnInfo::DATATYPE_COMMENT_DEFAULT ||
737  getValueAsString() == ""; // in case people delete default comment, allow blank also
738  else
739  return getValueAsString() == TableViewColumnInfo::DATATYPE_STRING_DEFAULT;
740  }
741  else if(getValueDataType() == TableViewColumnInfo::DATATYPE_NUMBER)
742  return getValueAsString() == TableViewColumnInfo::DATATYPE_NUMBER_DEFAULT;
743  else if(getValueDataType() == TableViewColumnInfo::DATATYPE_TIME)
744  return getValueAsString() == TableViewColumnInfo::DATATYPE_TIME_DEFAULT;
745  else
746  return false;
747 } // end isDefaultValue()
748 
749 //==============================================================================
750 // getDefaultValue
751 // returns default value if is value node
752 const std::string& ConfigurationTree::getDefaultValue(void) const
753 {
754  if(!isValueNode())
755  {
756  __SS__ << "Can only get default value from a value node! "
757  << "The node type is " << getNodeType() << __E__;
758 
759  ss << nodeDump() << __E__;
760  __SS_THROW__;
761  }
762 
763  if(getValueDataType() == TableViewColumnInfo::DATATYPE_STRING)
764  {
765  if(getValueType() == TableViewColumnInfo::TYPE_ON_OFF || getValueType() == TableViewColumnInfo::TYPE_TRUE_FALSE ||
766  getValueType() == TableViewColumnInfo::TYPE_YES_NO)
767  return TableViewColumnInfo::DATATYPE_BOOL_DEFAULT; // default to OFF, NO,
768  // FALSE
769  else if(getValueType() == TableViewColumnInfo::TYPE_COMMENT)
770  return TableViewColumnInfo::DATATYPE_COMMENT_DEFAULT; // in case people delete default comment, allow blank also
771  else
772  return TableViewColumnInfo::DATATYPE_STRING_DEFAULT;
773  }
774  else if(getValueDataType() == TableViewColumnInfo::DATATYPE_NUMBER)
775  return TableViewColumnInfo::DATATYPE_NUMBER_DEFAULT;
776  else if(getValueDataType() == TableViewColumnInfo::DATATYPE_TIME)
777  return TableViewColumnInfo::DATATYPE_TIME_DEFAULT;
778 
779  {
780  __SS__ << "Can only get default value from a value node! "
781  << "The node type is " << getNodeType() << __E__;
782 
783  ss << nodeDump() << __E__;
784  __SS_THROW__;
785  }
786 } // end isDefaultValue()
787 
788 //==============================================================================
789 // getValueType
790 // e.g. used to determine if node is type TYPE_DATA, TYPE_ON_OFF, etc.
791 const std::string& ConfigurationTree::getValueType(void) const
792 {
793  if(isValueNode())
794  return tableView_->getColumnInfo(col_).getType();
795  else if(isLinkNode() && isDisconnected())
796  return ConfigurationTree::VALUE_TYPE_DISCONNECTED;
797  else // just call all non-value nodes data
798  return ConfigurationTree::VALUE_TYPE_NODE;
799 } // end getValueType()
800 
801 //==============================================================================
802 // getColumnInfo
803 // only sensible for value node
804 const TableViewColumnInfo& ConfigurationTree::getColumnInfo(void) const
805 {
806  if(isValueNode())
807  return tableView_->getColumnInfo(col_);
808  else
809  {
810  __SS__ << "Can only get column info from a value node! "
811  << "The node type is " << getNodeType() << __E__;
812 
813  ss << nodeDump() << __E__;
814  __SS_THROW__;
815  }
816 } // end getColumnInfo()
817 
818 //==============================================================================
819 // getRow
820 const unsigned int& ConfigurationTree::getRow(void) const { return row_; }
821 
822 //==============================================================================
823 // getColumn
824 const unsigned int& ConfigurationTree::getColumn(void) const { return col_; }
825 
826 //==============================================================================
827 // getFieldRow
828 // return field's row (different handling for value vs. link node)
829 const unsigned int& ConfigurationTree::getFieldRow(void) const
830 {
831  if(isLinkNode())
832  {
833  // for links, need to use parent info to determine
834  return linkBackRow_;
835  }
836  else
837  return row_;
838 } // end getFieldRow()
839 
840 //==============================================================================
841 // getFieldColumn
842 // return field's column (different handling for value vs. link node)
843 const unsigned int& ConfigurationTree::getFieldColumn(void) const
844 {
845  if(isLinkNode())
846  {
847  // for links, need to use parent info to determine
848  return linkBackCol_;
849  }
850  else
851  return col_;
852 } // end getFieldColumn()
853 
854 //==============================================================================
855 // getChildLinkIndex
856 const std::string& ConfigurationTree::getChildLinkIndex(void) const
857 {
858  if(!isLinkNode())
859  {
860  __SS__ << "Can only get link ID from a link! "
861  << "The node type is " << getNodeType() << __E__;
862 
863  ss << nodeDump() << __E__;
864  __SS_THROW__;
865  }
866  return childLinkIndex_;
867 } // end getChildLinkIndex()
868 
869 //==============================================================================
870 // getValueName
871 // e.g. used to determine column name of value node
872 const std::string& ConfigurationTree::getValueName(void) const
873 {
874  if(isValueNode())
875  return tableView_->getColumnInfo(col_).getName();
876  else if(isLinkNode())
877  return linkColName_;
878  else
879  {
880  __SS__ << "Can only get value name of a value node!" << __E__;
881 
882  ss << nodeDump() << __E__;
883  __SS_THROW__;
884  }
885 } // end getValueName()
886 
887 //==============================================================================
888 // recurse
889 // Used by ConfigurationTree to handle / syntax of getNode
890 ConfigurationTree ConfigurationTree::recurse(const ConfigurationTree& tree,
891  const std::string& childPath,
892  bool doNotThrowOnBrokenUIDLinks,
893  const std::string& originalNodeString)
894 {
895  //__COUT__ << tree.row_ << " " << tree.col_ << __E__;
896  //__COUT__ << "childPath=" << childPath << " " << childPath.length() << __E__;
897  if(childPath.length() <= 1) // only "/" or ""
898  return tree;
899  return tree.recursiveGetNode(childPath, doNotThrowOnBrokenUIDLinks, originalNodeString);
900 } // end recurse()
901 
912 // std::string ConfigurationTree::getRecordFieldValueAsString(std::string fieldName) const
913 //{
914 // //enforce that starting point is a table node
915 // if(!isUIDNode())
916 // {
917 // __SS__ << "Can only get getRecordFieldValueAsString from a uid node! " <<
918 // "The node type is " << getNodeType() << __E__;
919 // __COUT__ << "\n" << ss.str() << __E__;
920 // __SS_THROW__;
921 // }
922 //
923 // unsigned int c = tableView_->findCol(fieldName);
924 // return tableView_->getDataView()[row_][c];
925 //}
926 
927 //==============================================================================
928 // getNode
929 // Connected to recursiveGetNode()
930 //
931 // nodeString can be a multi-part path using / delimiter
932 // use:
933 // getNode(/uid/col) or getNode(uid)->getNode(col)
934 //
935 // if doNotThrowOnBrokenUIDLinks
936 // then catch exceptions on UID links and call disconnected
937 ConfigurationTree ConfigurationTree::getNode(const std::string& nodeString, bool doNotThrowOnBrokenUIDLinks) const
938 {
939  return recursiveGetNode(nodeString, doNotThrowOnBrokenUIDLinks, "" /*originalNodeString*/);
940 } // end getNode() connected to recursiveGetNode()
941 ConfigurationTree ConfigurationTree::recursiveGetNode(const std::string& nodeString,
942  bool doNotThrowOnBrokenUIDLinks,
943  const std::string& originalNodeString) const
944 {
945  //__COUT__ << "nodeString=" << nodeString << " " << nodeString.length() << __E__;
946  //__COUT__ << "doNotThrowOnBrokenUIDLinks=" << doNotThrowOnBrokenUIDLinks <<
947  // __E__;
948 
949  // get nodeName (in case of / syntax)
950  if(nodeString.length() < 1)
951  {
952  __SS__ << "Invalid empty node name! Looking for child node from node '" << getValue() << "'..." << __E__;
953 
954  ss << nodeDump() << __E__;
955  __SS_THROW__;
956  }
957 
958  bool startingSlash = nodeString[0] == '/';
959 
960  std::string nodeName = nodeString.substr(startingSlash ? 1 : 0, nodeString.find('/', 1) - (startingSlash ? 1 : 0));
961  //__COUT__ << "nodeName=" << nodeName << " " << nodeName.length() << __E__;
962 
963  std::string childPath = nodeString.substr(nodeName.length() + (startingSlash ? 1 : 0));
964  //__COUT__ << "childPath=" << childPath << " " << childPath.length() << __E__;
965 
966  // if this tree is beginning at a configuration.. then go to uid, and vice versa
967 
968  try
969  {
970  //__COUT__ << row_ << " " << col_ << " " << groupId_ << " " << tableView_ <<
971  // __E__;
972  if(!table_)
973  {
974  // root node
975  // so return table node
976  return recurse(configMgr_->getNode(nodeName), childPath, doNotThrowOnBrokenUIDLinks, originalNodeString);
977  }
978  else if(row_ == TableView::INVALID && col_ == TableView::INVALID)
979  {
980  // table node
981 
982  if(!tableView_)
983  {
984  __SS__ << "Missing configView pointer! Likely attempting to access a "
985  "child node through a disconnected link node."
986  << __E__;
987 
988  ss << nodeDump() << __E__;
989  __SS_THROW__;
990  }
991 
992  // this node is table node, so return uid node considering groupid
993  return recurse(ConfigurationTree(configMgr_,
994  table_,
995  "", // no new groupId string, not a link
996  0 /*linkParentConfig_*/,
997  "", // link node name, not a link
998  "", // link node value, not a link
999  TableView::INVALID /*linkBackRow_*/,
1000  TableView::INVALID /*linkBackCol_*/,
1001  "", // ignored disconnected target name, not a link
1002  "", // ignored disconnected link id, not a link
1003  "",
1004  // if this node is group table node, consider that when getting rows
1005  (groupId_ == "") ? tableView_->findRow(tableView_->getColUID(), nodeName)
1006  : tableView_->findRowInGroup(tableView_->getColUID(), nodeName, groupId_, childLinkIndex_)),
1007  childPath,
1008  doNotThrowOnBrokenUIDLinks,
1009  originalNodeString);
1010  }
1011  else if(row_ == TableView::INVALID)
1012  {
1013  __SS__ << "Malformed ConfigurationTree" << __E__;
1014 
1015  ss << nodeDump() << __E__;
1016  __SS_THROW__;
1017  }
1018  else if(col_ == TableView::INVALID)
1019  {
1020  // this node is uid node, so return link, group link, disconnected, or value
1021  // node
1022 
1023  //__COUT__ << "nodeName=" << nodeName << " " << nodeName.length() <<
1024  // __E__;
1025 
1026  // if the value is a unique link ..
1027  // return a uid node!
1028  // if the value is a group link
1029  // return a table node with group string
1030  // else.. return value node
1031 
1032  if(!tableView_)
1033  {
1034  __SS__ << "Missing configView pointer! Likely attempting to access a "
1035  "child node through a disconnected link node."
1036  << __E__;
1037 
1038  ss << nodeDump() << __E__;
1039  __SS_THROW__;
1040  }
1041 
1042  unsigned int c = tableView_->findCol(nodeName);
1043  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
1044  bool isGroupLink, isLink;
1045  if((isLink = tableView_->getChildLink(c, isGroupLink, linkPair)) && !isGroupLink)
1046  {
1047  //__COUT__ << "nodeName=" << nodeName << " " << nodeName.length() <<
1048  // __E__; is a unique link, return uid node in new configuration
1049  // need new configuration pointer
1050  // and row of linkUID in new configuration
1051 
1052  const TableBase* childConfig;
1053  try
1054  {
1055  childConfig = configMgr_->getTableByName(tableView_->getDataView()[row_][linkPair.first]);
1056  childConfig->getView(); // get view as a test for an active view
1057 
1058  if(doNotThrowOnBrokenUIDLinks) // try a test of getting row
1059  {
1060  childConfig->getView().findRow(childConfig->getView().getColUID(), tableView_->getDataView()[row_][linkPair.second]);
1061  }
1062  }
1063  catch(...)
1064  {
1065  // __COUT_WARN__ << "Found disconnected node! (" <<
1066  // nodeName
1067  //<<
1068  // ":" <<
1069  // tableView_->getDataView()[row_][linkPair.first]
1070  //<< ")" << " at entry with UID " <<
1071  // tableView_->getDataView()[row_][tableView_->getColUID()]
1072  //<< __E__; do not recurse further
1073  return ConfigurationTree(configMgr_,
1074  0,
1075  "",
1076  table_, // linkParentConfig_
1077  nodeName,
1078  tableView_->getDataView()[row_][c], // this the link node field
1079  // associated value (matches
1080  // targeted column)
1081  row_ /*linkBackRow_*/,
1082  c /*linkBackCol_*/,
1083  tableView_->getDataView()[row_][linkPair.first], // give
1084  // disconnected
1085  // target name
1086  tableView_->getDataView()[row_][linkPair.second], // give
1087  // disconnected
1088  // link ID
1089  tableView_->getColumnInfo(c).getChildLinkIndex());
1090  }
1091 
1092  return recurse(ConfigurationTree( // this is a link node
1093  configMgr_,
1094  childConfig,
1095  "", // no new groupId string
1096  table_, // linkParentConfig_
1097  nodeName, // this is a link node
1098  tableView_->getDataView()[row_][c], // this the link node field
1099  // associated value (matches
1100  // targeted column)
1101  row_ /*linkBackRow_*/,
1102  c /*linkBackCol_*/,
1103  "", // ignore since is connected
1104  "", // ignore since is connected
1105  tableView_->getColumnInfo(c).getChildLinkIndex(),
1106  childConfig->getView().findRow(childConfig->getView().getColUID(), tableView_->getDataView()[row_][linkPair.second])),
1107  childPath,
1108  doNotThrowOnBrokenUIDLinks,
1109  originalNodeString);
1110  }
1111  else if(isLink)
1112  {
1113  //__COUT__ << "nodeName=" << nodeName << " " << nodeName.length() <<
1114  // __E__; is a group link, return new configuration with group string
1115  // need new configuration pointer
1116  // and group string
1117 
1118  const TableBase* childConfig;
1119  try
1120  {
1121  childConfig = configMgr_->getTableByName(tableView_->getDataView()[row_][linkPair.first]);
1122  childConfig->getView(); // get view as a test for an active view
1123  }
1124  catch(...)
1125  {
1126  if(tableView_->getDataView()[row_][linkPair.first] != TableViewColumnInfo::DATATYPE_LINK_DEFAULT)
1127  __COUT_WARN__ << "Found disconnected node! Failed link target "
1128  "from nodeName="
1129  << nodeName << " to table:id=" << tableView_->getDataView()[row_][linkPair.first] << ":"
1130  << tableView_->getDataView()[row_][linkPair.second] << __E__;
1131 
1132  // do not recurse further
1133  return ConfigurationTree(configMgr_,
1134  0,
1135  tableView_->getDataView()[row_][linkPair.second], // groupID
1136  table_, // linkParentConfig_
1137  nodeName,
1138  tableView_->getDataView()[row_][c], // this the link node field
1139  // associated value (matches
1140  // targeted column)
1141  row_ /*linkBackRow_*/,
1142  c /*linkBackCol_*/,
1143  tableView_->getDataView()[row_][linkPair.first], // give
1144  // disconnected
1145  // target name
1146  tableView_->getDataView()[row_][linkPair.second], // give
1147  // disconnected
1148  // target name
1149  tableView_->getColumnInfo(c).getChildLinkIndex());
1150  }
1151 
1152  return recurse(ConfigurationTree( // this is a link node
1153  configMgr_,
1154  childConfig,
1155  tableView_->getDataView()[row_][linkPair.second], // groupId string
1156  table_, // linkParentConfig_
1157  nodeName, // this is a link node
1158  tableView_->getDataView()[row_][c], // this the link node field
1159  // associated value (matches
1160  // targeted column)
1161  row_ /*linkBackRow_*/,
1162  c /*linkBackCol_*/,
1163  "", // ignore since is connected
1164  "", // ignore since is connected
1165  tableView_->getColumnInfo(c).getChildLinkIndex()),
1166  childPath,
1167  doNotThrowOnBrokenUIDLinks,
1168  originalNodeString);
1169  }
1170  else
1171  {
1172  //__COUT__ << "nodeName=" << nodeName << " " << nodeName.length() <<
1173  // __E__; return value node
1174  return ConfigurationTree(configMgr_,
1175  table_,
1176  "",
1177  0 /*linkParentConfig_*/,
1178  "",
1179  "",
1180  TableView::INVALID /*linkBackRow_*/,
1181  TableView::INVALID /*linkBackCol_*/,
1182  "",
1183  "" /*disconnectedLinkID*/,
1184  "",
1185  row_,
1186  c);
1187  }
1188  }
1189  }
1190  catch(std::runtime_error& e)
1191  {
1192  __SS__ << "\n\nError occurred descending from node '" << getValue() << "' in table '" << getTableName() << "' looking for child '" << nodeName
1193  << "'\n\n"
1194  << __E__;
1195  ss << "The original node search string was '" << originalNodeString << ".'" << __E__;
1196  ss << "--- Additional error detail: \n\n" << e.what() << __E__;
1197 
1198  ss << nodeDump() << __E__;
1199  __SS_ONLY_THROW__;
1200  }
1201  catch(...)
1202  {
1203  __SS__ << "\n\nError occurred descending from node '" << getValue() << "' in table '" << getTableName() << "' looking for child '" << nodeName
1204  << "'\n\n"
1205  << __E__;
1206  ss << "The original node search string was '" << originalNodeString << ".'" << __E__;
1207 
1208  ss << nodeDump() << __E__;
1209  __SS_ONLY_THROW__;
1210  }
1211 
1212  // this node is value node, so has no node to choose from
1213  __SS__ << "\n\nError occurred descending from node '" << getValue() << "' in table '" << getTableName() << "' looking for child '" << nodeName << "'\n\n"
1214  << "Invalid depth! getNode() called from a value point in the Configuration Tree." << __E__;
1215  ss << "The original node search string was '" << originalNodeString << ".'" << __E__;
1216 
1217  ss << nodeDump() << __E__;
1218  __SS_ONLY_THROW__; // this node is value node, cant go any deeper!
1219 } // end recursiveGetNode()
1220 
1221 //==============================================================================
1222 // nodeDump
1223 // Useful for debugging a node failure, like when throwing an exception
1224 std::string ConfigurationTree::nodeDump(void) const
1225 {
1226  __SS__ << __E__ << __E__;
1227 
1228  ss << "Row=" << (int)row_ << ", Col=" << (int)col_ << ", TablePointer=" << table_ << __E__;
1229 
1230  try
1231  {
1232  ss << "\n\n" << StringMacros::stackTrace() << __E__ << __E__;
1233  }
1234  catch(...)
1235  {
1236  } // ignore errors
1237 
1238  ss << "ConfigurationTree::nodeDump() "
1239  "=====================================\nConfigurationTree::nodeDump():"
1240  << __E__;
1241 
1242  // try each level of debug.. and ignore errors
1243  try
1244  {
1245  ss << "\t"
1246  << "Error occurred from node '" << getValueAsString() << "'..." << __E__;
1247  }
1248  catch(...)
1249  {
1250  } // ignore errors
1251  try
1252  {
1253  ss << "\t"
1254  << "Error occurred from node '" << getValue() << "' in table '" << getTableName() << ".'" << __E__;
1255  }
1256  catch(...)
1257  {
1258  } // ignore errors
1259  try
1260  {
1261  auto children = getChildrenNames();
1262  ss << "\t"
1263  << "Here is the list of possible children (count = " << children.size() << "):" << __E__;
1264  for(auto& child : children)
1265  ss << "\t\t" << child << __E__;
1266  if(tableView_)
1267  {
1268  ss << "\n\nHere is the culprit table printout:\n\n";
1269  tableView_->print(ss);
1270  }
1271  }
1272  catch(...)
1273  {
1274  } // ignore errors trying to show children
1275 
1276  ss << "end ConfigurationTree::nodeDump() =====================================" << __E__;
1277 
1278  return ss.str();
1279 } // end nodeDump()
1280 
1281 //==============================================================================
1282 ConfigurationTree ConfigurationTree::getBackNode(std::string nodeName, unsigned int backSteps) const
1283 {
1284  for(unsigned int i = 0; i < backSteps; i++)
1285  nodeName = nodeName.substr(0, nodeName.find_last_of('/'));
1286 
1287  return getNode(nodeName);
1288 } // end getBackNode()
1289 
1290 //==============================================================================
1291 ConfigurationTree ConfigurationTree::getForwardNode(std::string nodeName, unsigned int forwardSteps) const
1292 {
1293  unsigned int s = 0;
1294 
1295  // skip all leading /'s
1296  while(s < nodeName.length() && nodeName[s] == '/')
1297  ++s;
1298 
1299  for(unsigned int i = 0; i < forwardSteps; i++)
1300  s = nodeName.find('/', s) + 1;
1301 
1302  return getNode(nodeName.substr(0, s));
1303 } // end getForwardNode()
1304 
1305 //==============================================================================
1306 // isValueNode
1307 // if true, then this is a leaf node, i.e. there can be no children, only a value
1308 bool ConfigurationTree::isValueNode(void) const { return (row_ != TableView::INVALID && col_ != TableView::INVALID); } // end isValueNode()
1309 
1310 //==============================================================================
1311 // isValueBoolType
1312 // if true, then this is a leaf node with BOOL type
1313 bool ConfigurationTree::isValueBoolType(void) const { return isValueNode() && tableView_->getColumnInfo(col_).isBoolType(); } // end isValueBoolType()
1314 
1315 //==============================================================================
1316 // isValueNumberDataType
1317 // if true, then this is a leaf node with NUMBER data type
1318 bool ConfigurationTree::isValueNumberDataType(void) const
1319 {
1320  return isValueNode() && tableView_->getColumnInfo(col_).isNumberDataType();
1321 } // end isValueBoolType()
1322 
1323 //==============================================================================
1324 // isDisconnected
1325 // if true, then this is a disconnected node, i.e. there is a configuration link missing
1326 // (this is possible when the configuration is loaded in stages and the complete tree
1327 // may not be available, yet)
1328 bool ConfigurationTree::isDisconnected(void) const
1329 {
1330  if(!isLinkNode())
1331  {
1332  __SS__ << "\n\nError occurred testing link connection at node with value '" << getValue() << "' in table '" << getTableName() << "'\n\n" << __E__;
1333  ss << "This is not a Link node! It is node type '" << getNodeType() << ".' Only a Link node can be disconnected." << __E__;
1334 
1335  ss << nodeDump() << __E__;
1336  __SS_ONLY_THROW__;
1337  }
1338 
1339  return !table_ || !tableView_;
1340 } // end isDisconnected()
1341 
1342 //==============================================================================
1343 // isLinkNode
1344 // if true, then this is a link node
1345 bool ConfigurationTree::isLinkNode(void) const { return linkColName_ != ""; }
1346 
1347 //==============================================================================
1348 // getNodeType
1349 // return node type as string
1350 const std::string ConfigurationTree::NODE_TYPE_GROUP_TABLE = "GroupTableNode";
1351 const std::string ConfigurationTree::NODE_TYPE_TABLE = "TableNode";
1352 const std::string ConfigurationTree::NODE_TYPE_GROUP_LINK = "GroupLinkNode";
1353 const std::string ConfigurationTree::NODE_TYPE_UID_LINK = "UIDLinkNode";
1354 const std::string ConfigurationTree::NODE_TYPE_VALUE = "ValueNode";
1355 const std::string ConfigurationTree::NODE_TYPE_UID = "UIDNode";
1356 const std::string ConfigurationTree::NODE_TYPE_ROOT = "RootNode";
1357 
1358 std::string ConfigurationTree::getNodeType(void) const
1359 {
1360  if(!table_)
1361  return ConfigurationTree::NODE_TYPE_ROOT;
1362  if(isTableNode() && groupId_ != "")
1363  return ConfigurationTree::NODE_TYPE_GROUP_TABLE;
1364  if(isTableNode())
1365  return ConfigurationTree::NODE_TYPE_TABLE;
1366  if(isGroupLinkNode())
1367  return ConfigurationTree::NODE_TYPE_GROUP_LINK;
1368  if(isLinkNode())
1369  return ConfigurationTree::NODE_TYPE_UID_LINK;
1370  if(isValueNode())
1371  return ConfigurationTree::NODE_TYPE_VALUE;
1372  return ConfigurationTree::NODE_TYPE_UID;
1373 } // end getNodeType()
1374 
1375 //==============================================================================
1376 // isGroupLinkNode
1377 // if true, then this is a group link node
1378 bool ConfigurationTree::isGroupLinkNode(void) const { return (isLinkNode() && groupId_ != ""); }
1379 
1380 //==============================================================================
1381 // isUIDLinkNode
1382 // if true, then this is a uid link node
1383 bool ConfigurationTree::isUIDLinkNode(void) const { return (isLinkNode() && groupId_ == ""); } // end isUIDLinkNode()
1384 
1385 //==============================================================================
1386 // isGroupIDNode
1387 // if true, then this is a Group ID node
1388 bool ConfigurationTree::isGroupIDNode(void) const { return (isValueNode() && tableView_->getColumnInfo(col_).isGroupID()); } // end isGroupIDNode()
1389 
1390 //==============================================================================
1391 // isUIDNode
1392 // if true, then this is a uid node
1393 bool ConfigurationTree::isUIDNode(void) const { return (row_ != TableView::INVALID && col_ == TableView::INVALID); }
1394 
1395 //==============================================================================
1396 // getCommonFields
1397 // wrapper for ...recursiveGetCommonFields
1398 //
1399 // returns common fields in order encountered
1400 // including through UID links depending on depth specified
1401 //
1402 // Field := {Table, UID, Column Name, Relative Path, TableViewColumnInfo}
1403 //
1404 // if fieldAcceptList or fieldRejectList are not empty,
1405 // then reject any that are not in field accept filter list
1406 // and reject any that are in field reject filter list
1407 //
1408 // will only go to specified depth looking for fields
1409 // (careful to prevent infinite loops in tree navigation)
1410 //
1411 std::vector<ConfigurationTree::RecordField> ConfigurationTree::getCommonFields(const std::vector<std::string /*uid*/>& recordList,
1412  const std::vector<std::string /*relative-path*/>& fieldAcceptList,
1413  const std::vector<std::string /*relative-path*/>& fieldRejectList,
1414  unsigned int depth,
1415  bool autoSelectFilterFields) const
1416 {
1417  // enforce that starting point is a table node
1418  if(!isRootNode() && !isTableNode())
1419  {
1420  __SS__ << "Can only get getCommonFields from a root or table node! "
1421  << "The node type is " << getNodeType() << __E__;
1422 
1423  ss << nodeDump() << __E__;
1424  __SS_THROW__;
1425  }
1426 
1427  std::vector<ConfigurationTree::RecordField> fieldCandidateList;
1428  std::vector<int> fieldCount; //-1 := guaranteed, else count must match num of records
1429 
1430  --depth; // decrement for recursion
1431 
1432  // for each record in <record list>
1433  // loop through all record's children
1434  // if isValueNode (value nodes are possible field candidates!)
1435  // if first uid record
1436  // add field to <field candidates list> if in <field filter list>
1437  // mark <field count> as guaranteed -1 (all these fields must be common
1438  // for UIDs in same table)
1439  // else not first uid record, do not need to check, must be same as first
1440  // record! else if depth > 0 and UID-Link Node recursively (call
1441  // recursiveGetCommonFields())
1442  // =====================
1443  // Start recursiveGetCommonFields()
1444  // --depth;
1445  // loop through all children
1446  // if isValueNode (value nodes are possible field candidates!)
1447  // if first uid record
1448  // add field to <field candidates list> if in <field
1449  // filter list> initial mark <field count> as 1
1450  // else
1451  // if field is in <field candidates list>,
1452  // increment <field count> for field candidate
1453  // else if field is not in list, ignore field
1454  // else if depth > 0 and is UID-Link
1455  // if Link Table/UID pair is not found in <field candidates
1456  // list> (avoid endless loops through tree)
1457  // recursiveGetCommonFields()
1458  // =====================
1459  //
1460  //
1461  // loop through all field candidates
1462  // remove those with <field count> != num of records
1463  //
1464  //
1465  // return result
1466 
1467  bool found; // used in loops
1468  // auto tableName = isRootNode()?"/":getTableName(); //all records will share this
1469  // table name
1470 
1471  // if no records, just return table fields
1472  if(!recordList.size() && tableView_)
1473  {
1474  const std::vector<TableViewColumnInfo>& colInfo = tableView_->getColumnsInfo();
1475 
1476  for(unsigned int col = 0; col < colInfo.size(); ++col)
1477  {
1478  //__COUT__ << "Considering field " <<
1479  // colInfo[col].getName() << __E__;
1480 
1481  // check field accept filter list
1482  found = fieldAcceptList.size() ? false : true; // accept if no filter
1483  // list
1484  for(const auto& fieldFilter : fieldAcceptList)
1485  if(StringMacros::wildCardMatch(fieldFilter, colInfo[col].getName()))
1486  {
1487  found = true;
1488  break;
1489  }
1490 
1491  if(found)
1492  {
1493  // check field reject filter list
1494 
1495  found = true; // accept if no filter list
1496  for(const auto& fieldFilter : fieldRejectList)
1497  if(StringMacros::wildCardMatch(fieldFilter, colInfo[col].getName()))
1498  {
1499  found = false; // reject if match
1500  break;
1501  }
1502  }
1503 
1504  // if found, new field (since this is first record)
1505  if(found)
1506  {
1507  //__COUT__ << "FOUND field " <<
1508  // colInfo[col].getName() << __E__;
1509 
1510  if(colInfo[col].isChildLink())
1511  {
1512  //__COUT__ << "isGroupLinkNode " << colInfo[col].getName() << __E__;
1513 
1514  // must get column info differently for group link column
1515 
1516  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
1517  bool isGroupLink;
1518  tableView_->getChildLink(col, isGroupLink, linkPair);
1519 
1520  // add both link columns
1521 
1522  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1523  "", // uid
1524  tableView_->getColumnInfo(linkPair.first).getName(),
1525  "", // relative path, not including columnName_
1526  &tableView_->getColumnInfo(linkPair.first)));
1527  fieldCount.push_back(-1); // mark guaranteed field
1528 
1529  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1530  "", // uid
1531  tableView_->getColumnInfo(linkPair.second).getName(),
1532  "", // relative path, not including columnName_
1533  &tableView_->getColumnInfo(linkPair.second)));
1534  fieldCount.push_back(-1); // mark guaranteed field
1535  }
1536  else // value node
1537  {
1538  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1539  "", // uid
1540  colInfo[col].getName(),
1541  "", // relative path, not including columnName_
1542  &colInfo[col]));
1543  fieldCount.push_back(1); // init count to 1
1544  }
1545  }
1546  } // end table column loop
1547  } // end no record handling
1548 
1549  for(unsigned int i = 0; i < recordList.size(); ++i)
1550  {
1551  //__COUT__ << "Checking " << recordList[i] << __E__;
1552  ConfigurationTree node = getNode(recordList[i]);
1553 
1554  node.recursiveGetCommonFields(fieldCandidateList,
1555  fieldCount,
1556  fieldAcceptList,
1557  fieldRejectList,
1558  depth,
1559  "", // relativePathBase
1560  !i // continue inFirstRecord (or not) depth search
1561  );
1562 
1563  } // end record loop
1564 
1565  __COUT__ << "======================= check for count = " <<
1566  (int)recordList.size() << __E__;
1567 
1568  // loop through all field candidates
1569  // remove those with <field count> != num of records
1570  for(unsigned int i = 0; i < fieldCandidateList.size(); ++i)
1571  {
1572  __COUT__ << "Checking " << fieldCandidateList[i].relativePath_ <<
1573  fieldCandidateList[i].columnName_ << " = " <<
1574  fieldCount[i] << __E__;
1575  if(recordList.size() != 0 && fieldCount[i] != -1 &&
1576  fieldCount[i] != (int)recordList.size())
1577  {
1578  //__COUT__ << "Erasing " << fieldCandidateList[i].relativePath_ <<
1579  // fieldCandidateList[i].columnName_ << __E__;
1580 
1581  fieldCount.erase(fieldCount.begin() + i);
1582  fieldCandidateList.erase(fieldCandidateList.begin() + i);
1583  --i; // rewind to look at next after deleted
1584  }
1585  }
1586 
1587  // for(unsigned int i=0;i<fieldCandidateList.size();++i)
1588  // __COUT__ << "Pre-Final " << fieldCandidateList[i].relativePath_ <<
1589  // fieldCandidateList[i].columnName_ << __E__;
1590 
1591  if(autoSelectFilterFields)
1592  {
1593  // filter for just 3 of the best filter fields
1594  // i.e. preference for GroupID, On/Off, and FixedChoice fields.
1595  std::set<std::pair<unsigned int /*fieldPriority*/, unsigned int /*fieldIndex*/>> prioritySet;
1596 
1597  unsigned int highestPriority = 0;
1598  unsigned int priorityPenalty;
1599  for(unsigned int i = 0; i < fieldCandidateList.size(); ++i)
1600  {
1601  // __COUT__ << "Option " << fieldCandidateList[i].relativePath_
1602  //<< fieldCandidateList[i].columnName_ << " : " <<
1603  // fieldCandidateList[i].columnInfo_->getType() << ":" <<
1604  // fieldCandidateList[i].columnInfo_->getDataType() <<
1605  //__E__;
1606 
1607  priorityPenalty =
1608  std::count(fieldCandidateList[i].relativePath_.begin(), fieldCandidateList[i].relativePath_.end(), '/') * 20; // penalize if not top level
1609 
1610  if(fieldCandidateList[i].columnInfo_->isBoolType())
1611  {
1612  prioritySet.emplace(std::make_pair(0 + priorityPenalty /*fieldPriority*/, i /*fieldIndex*/));
1613  if(highestPriority < 0 + priorityPenalty)
1614  highestPriority = 0 + priorityPenalty;
1615  }
1616  else if(fieldCandidateList[i].columnInfo_->isGroupID())
1617  {
1618  prioritySet.emplace(std::make_pair(1 + priorityPenalty /*fieldPriority*/, i /*fieldIndex*/));
1619  if(highestPriority < 1 + priorityPenalty)
1620  highestPriority = 1 + priorityPenalty;
1621  }
1622  else if(fieldCandidateList[i].columnInfo_->getType() == TableViewColumnInfo::TYPE_FIXED_CHOICE_DATA)
1623  {
1624  prioritySet.emplace(std::make_pair(3 + priorityPenalty /*fieldPriority*/, i /*fieldIndex*/));
1625  if(highestPriority < 3 + priorityPenalty)
1626  highestPriority = 3 + priorityPenalty;
1627  }
1628  else if(fieldCandidateList[i].columnInfo_->getType() == TableViewColumnInfo::TYPE_DATA)
1629  {
1630  prioritySet.emplace(std::make_pair(10 + priorityPenalty /*fieldPriority*/, i /*fieldIndex*/));
1631  if(highestPriority < 10 + priorityPenalty)
1632  highestPriority = 10 + priorityPenalty;
1633  }
1634  else // skip other fields and mark for erasing
1635  {
1636  fieldCandidateList[i].tableName_ = ""; // clear table name as indicator for erase
1637  continue;
1638  }
1639 
1640  } // done ranking fields
1641 
1642  __COUTV__(StringMacros::setToString(prioritySet));
1643 
1644  // now choose the top 3, and delete the rest
1645  // clear table name to indicate field should be erased
1646  {
1647  unsigned int cnt = 0;
1648  for(const auto& priorityFieldIndex : prioritySet)
1649  if(++cnt > 3) // then mark for erasing
1650  {
1651  // __COUT__ << cnt << " marking " <<
1652  // fieldCandidateList[
1653  // priorityFieldIndex.second].relativePath_
1654  //<<
1655  // fieldCandidateList[priorityFieldIndex.second].columnName_
1656  //<<
1657  // __E__;
1658  fieldCandidateList[priorityFieldIndex.second].tableName_ = ""; // clear table name as indicator for erase
1659  }
1660  }
1661 
1662  for(unsigned int i = 0; i < fieldCandidateList.size(); ++i)
1663  {
1664  if(fieldCandidateList[i].tableName_ == "") // then erase
1665  {
1666  // __COUT__ << "Erasing " <<
1667  // fieldCandidateList[i].relativePath_
1668  //<< fieldCandidateList[i].columnName_ << __E__;
1669  fieldCandidateList.erase(fieldCandidateList.begin() + i);
1670  --i; // rewind to look at next after deleted
1671  }
1672  }
1673  } // end AUTO filter field selection
1674 
1675  for(unsigned int i = 0; i < fieldCandidateList.size(); ++i)
1676  __COUT__ << "Final " << fieldCandidateList[i].relativePath_ << fieldCandidateList[i].columnName_ << __E__;
1677 
1678  return fieldCandidateList;
1679 } // end getCommonFields()
1680 
1681 //==============================================================================
1682 // getUniqueValuesForField
1683 //
1684 // returns sorted unique values for the specified records and field
1685 // Note: treat GroupIDs special, parse the | out of the value to get the distinct values.
1686 //
1687 std::set<std::string /*unique-value*/> ConfigurationTree::getUniqueValuesForField(const std::vector<std::string /*relative-path*/>& recordList,
1688  const std::string& fieldName,
1689  std::string* fieldGroupIDChildLinkIndex /* =0 */) const
1690 {
1691  if(fieldGroupIDChildLinkIndex)
1692  *fieldGroupIDChildLinkIndex = "";
1693 
1694  // enforce that starting point is a table node
1695  if(!isTableNode())
1696  {
1697  __SS__ << "Can only get getCommonFields from a table node! "
1698  << "The node type is " << getNodeType() << __E__;
1699 
1700  ss << nodeDump() << __E__;
1701  __SS_THROW__;
1702  }
1703 
1704  std::set<std::string /*unique-value*/> uniqueValues;
1705 
1706  // for each record in <record list>
1707  // emplace value at field into set
1708  //
1709  // return result
1710 
1711  // if no records, just return fieldGroupIDChildLinkIndex
1712  if(!recordList.size() && tableView_ && fieldGroupIDChildLinkIndex)
1713  {
1714  const TableViewColumnInfo& colInfo = tableView_->getColumnInfo(tableView_->findCol(fieldName));
1715 
1716  if(colInfo.isGroupID())
1717  *fieldGroupIDChildLinkIndex = colInfo.getChildLinkIndex();
1718 
1719  } // end no records
1720 
1721  for(unsigned int i = 0; i < recordList.size(); ++i)
1722  {
1723  //__COUT__ << "Checking " << recordList[i] << __E__;
1724 
1725  // Note: that ConfigurationTree maps both fields associated with a link
1726  // to the same node instance.
1727  // The behavior is likely not expected as response for this function..
1728  // so for links return actual value for field name specified
1729  // i.e. if Table of link is requested give that; if linkID is requested give
1730  // that. use TRUE in getValueAsString for proper behavior
1731 
1732  ConfigurationTree node = getNode(recordList[i]).getNode(fieldName);
1733 
1734  if(node.isGroupIDNode())
1735  {
1736  // handle groupID node special
1737 
1738  //__COUT__ << "GroupID field " << fieldName << __E__;
1739 
1740  // first time, get field's GroupID Child Link Index, if applicable
1741  if(i == 0 && fieldGroupIDChildLinkIndex)
1742  *fieldGroupIDChildLinkIndex = node.getColumnInfo().getChildLinkIndex();
1743 
1744  // return set of groupIDs individually
1745 
1746  std::set<std::string> setOfGroupIDs = node.getSetOfGroupIDs();
1747  for(auto& groupID : setOfGroupIDs)
1748  uniqueValues.emplace(groupID);
1749  }
1750  else // normal record, return value as string
1751  uniqueValues.emplace(node.getValueAsString(true));
1752 
1753  } // end record loop
1754 
1755  return uniqueValues;
1756 } // end getUniqueValuesForField()
1757 
1758 //==============================================================================
1759 // recursiveGetCommonFields
1760 // wrapper is ...getCommonFields
1761 void ConfigurationTree::recursiveGetCommonFields(std::vector<ConfigurationTree::RecordField>& fieldCandidateList,
1762  std::vector<int>& fieldCount,
1763  const std::vector<std::string /*relative-path*/>& fieldAcceptList,
1764  const std::vector<std::string /*relative-path*/>& fieldRejectList,
1765  unsigned int depth,
1766  const std::string& relativePathBase,
1767  bool inFirstRecord) const
1768 {
1769  //__COUT__ << depth << ":relativePathBase " << relativePathBase <<
1770  // " + " << inFirstRecord <<__E__;
1771  --depth;
1772 
1773  // clang-format off
1774  // =====================
1775  // Start recursiveGetCommonFields()
1776  // --depth;
1777  // loop through all children
1778  // if isValueNode (value nodes are possible field candidates!)
1779  // if first uid record
1780  // add field to <field candidates list> if in <field filter list>
1781  // initial mark <field count> as 1
1782  // else
1783  // if field is in list,
1784  // increment count for field candidate
1785  // //?increment fields in list count for record
1786  // else if field is not in list, discard field
1787  // else if depth > 0 and is UID-Link
1788  // if Link Table/UID pair is not found in <field candidates list>
1789  // (avoid endless loops through tree)
1790  // recursiveGetCommonFields()
1791  // =====================
1792  // clang-format on
1793 
1794  bool found; // used in loops
1795  auto tableName = getTableName(); // all fields will share this table name
1796  auto uid = getUIDAsString(); // all fields will share this uid
1797  unsigned int j;
1798 
1799  auto recordChildren = getChildren();
1800  for(const auto& fieldNode : recordChildren)
1801  {
1802  //__COUT__ << "All... " << fieldNode.second.getNodeType() <<
1803  // " -- " << (relativePathBase + fieldNode.first) <<
1804  // " + " << inFirstRecord <<__E__;
1805 
1806  if(fieldNode.second.isValueNode() || fieldNode.second.isGroupLinkNode())
1807  {
1808  // skip author and record insertion time
1809  if(fieldNode.second.isValueNode())
1810  {
1811  if(fieldNode.second.getColumnInfo().getType() == TableViewColumnInfo::TYPE_AUTHOR ||
1812  fieldNode.second.getColumnInfo().getType() == TableViewColumnInfo::TYPE_TIMESTAMP)
1813  continue;
1814 
1815  //__COUT__ << "isValueNode " << fieldNode.first << __E__;
1816  }
1817 
1818  if(inFirstRecord) // first uid record
1819  {
1820  //__COUT__ << "Checking... " << fieldNode.second.getNodeType() <<
1821  // " -- " << (relativePathBase + fieldNode.first) <<
1822  // "-- depth=" << depth << __E__;
1823 
1824  // check field accept filter list
1825  found = fieldAcceptList.size() ? false : true; // accept if no filter
1826  // list
1827  for(const auto& fieldFilter : fieldAcceptList)
1828  if(fieldFilter.find('/') != std::string::npos)
1829  {
1830  // filter is for full path, so add relative path base
1831  if(StringMacros::wildCardMatch(fieldFilter, relativePathBase + fieldNode.first))
1832  {
1833  found = true;
1834  break;
1835  }
1836  }
1837  else if(StringMacros::wildCardMatch(fieldFilter, fieldNode.first))
1838  {
1839  found = true;
1840  break;
1841  }
1842 
1843  if(found)
1844  {
1845  // check field reject filter list
1846 
1847  found = true; // accept if no filter list
1848  for(const auto& fieldFilter : fieldRejectList)
1849  if(fieldFilter.find('/') != std::string::npos)
1850  {
1851  // filter is for full path, so add relative path base
1852  if(StringMacros::wildCardMatch(fieldFilter, relativePathBase + fieldNode.first))
1853  {
1854  found = false; // reject if match
1855  break;
1856  }
1857  }
1858  else if(StringMacros::wildCardMatch(fieldFilter, fieldNode.first))
1859  {
1860  found = false; // reject if match
1861  break;
1862  }
1863  }
1864 
1865  // if found, new field (since this is first record)
1866  if(found)
1867  {
1868  //__COUT__ << "FOUND field " <<
1869  // (relativePathBase + fieldNode.first) << __E__;
1870 
1871  if(fieldNode.second.isGroupLinkNode())
1872  {
1873  //__COUT__ << "isGroupLinkNode " << fieldNode.first << __E__;
1874 
1875  // must get column info differently for group link column
1876 
1877  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
1878  bool isGroupLink;
1879  tableView_->getChildLink(tableView_->findCol(fieldNode.first), isGroupLink, linkPair);
1880 
1881  // add both link columns
1882 
1883  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1884  uid,
1885  tableView_->getColumnInfo(linkPair.first).getName(),
1886  relativePathBase, // relative path, not including columnName_
1887  &tableView_->getColumnInfo(linkPair.first)));
1888  fieldCount.push_back(1); // init count to 1
1889 
1890  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1891  uid,
1892  tableView_->getColumnInfo(linkPair.second).getName(),
1893  relativePathBase, // relative path, not including columnName_
1894  &tableView_->getColumnInfo(linkPair.second)));
1895  fieldCount.push_back(1); // init count to 1
1896  }
1897  else // value node
1898  {
1899  fieldCandidateList.push_back(ConfigurationTree::RecordField(tableName,
1900  uid,
1901  fieldNode.first,
1902  relativePathBase, // relative path, not including columnName_
1903  &fieldNode.second.getColumnInfo()));
1904  fieldCount.push_back(1); // init count to 1
1905  }
1906  }
1907  }
1908  else // not first record
1909  {
1910  // if field is in <field candidates list>, increment <field count>
1911  // else ignore
1912  for(j = 0; j < fieldCandidateList.size(); ++j)
1913  {
1914  if((relativePathBase + fieldNode.first) ==
1915  (fieldCandidateList[j].relativePath_ + fieldCandidateList[j].columnName_))
1916  {
1917  //__COUT__ << "incrementing " << j <<
1918  // " " << fieldCandidateList[j].relativePath_ << __E__;
1919  // found, so increment <field count>
1920  ++fieldCount[j];
1921  if(fieldNode.second.isGroupLinkNode() && j+1 < fieldCandidateList.size())
1922  ++fieldCount[j+1]; //increment associated link index too!
1923  break;
1924  }
1925  }
1926  }
1927  } // end value and group link node handling
1928  else if(fieldNode.second.isUIDLinkNode())
1929  {
1930  //__COUT__ << "isUIDLinkNode " << (relativePathBase + fieldNode.first) <<
1931  // " + " << inFirstRecord << __E__;
1932 
1933  if(inFirstRecord) // first uid record
1934  {
1935  // check field accept filter list
1936  found = fieldAcceptList.size() ? false : true; // accept if no filter list
1937  for(const auto& fieldFilter : fieldAcceptList)
1938  if(fieldFilter.find('/') != std::string::npos)
1939  {
1940  // filter is for full path, so add relative path base
1941  if(StringMacros::wildCardMatch(fieldFilter, relativePathBase + fieldNode.first))
1942  {
1943  found = true;
1944  break;
1945  }
1946  }
1947  else if(StringMacros::wildCardMatch(fieldFilter, fieldNode.first))
1948  {
1949  found = true;
1950  break;
1951  }
1952 
1953  if(found)
1954  {
1955  // check field reject filter list
1956 
1957  found = true; // accept if no filter list
1958  for(const auto& fieldFilter : fieldRejectList)
1959  if(fieldFilter.find('/') != std::string::npos)
1960  {
1961  // filter is for full path, so add relative path base
1962  if(StringMacros::wildCardMatch(fieldFilter, relativePathBase + fieldNode.first))
1963  {
1964  found = false; // reject if match
1965  break;
1966  }
1967  }
1968  else if(StringMacros::wildCardMatch(fieldFilter, fieldNode.first))
1969  {
1970  found = false; // reject if match
1971  break;
1972  }
1973  }
1974 
1975  //__COUTV__(found);
1976 
1977  // if found, new field (since this is first record)
1978  if(found)
1979  {
1980  std::pair<unsigned int /*link col*/, unsigned int /*link id col*/> linkPair;
1981  bool isGroupLink;
1982 
1983  //__COUTV__(fieldNode.first);
1984  tableView_->getChildLink(tableView_->findCol(fieldNode.first), isGroupLink, linkPair);
1985 
1986  // add both link columns
1987 
1988  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1989  uid,
1990  tableView_->getColumnInfo(linkPair.first).getName(),
1991  relativePathBase, // relative path, not including columnName_
1992  &tableView_->getColumnInfo(linkPair.first)));
1993  fieldCount.push_back(1); // init count to 1
1994 
1995  fieldCandidateList.push_back(ConfigurationTree::RecordField(table_->getTableName(),
1996  uid,
1997  tableView_->getColumnInfo(linkPair.second).getName(),
1998  relativePathBase, // relative path, not including columnName_
1999  &tableView_->getColumnInfo(linkPair.second)));
2000  fieldCount.push_back(1); // init count to 1
2001  }
2002  }
2003  else // not first record
2004  {
2005  // if link fields (MUST BE 2) is in <field candidates list>, increment <field count>
2006  // else ignore
2007  for(j = 0; j < fieldCandidateList.size()-1; ++j)
2008  {
2009  if((relativePathBase + fieldNode.first) ==
2010  (fieldCandidateList[j].relativePath_ + fieldCandidateList[j].columnName_))
2011  {
2012  //__COUT__ << "incrementing " << j <<
2013  // " " << fieldCandidateList[j].relativePath_ << __E__;
2014  // found, so increment <field count>
2015  ++fieldCount[j];
2016  ++fieldCount[j+1]; //increment associated link index too!
2017  break;
2018  }
2019  }
2020  }
2021 
2022  //if depth remaining, then follow link, recursively!
2023  if(depth > 0 && !fieldNode.second.isDisconnected())
2024  fieldNode.second.recursiveGetCommonFields(fieldCandidateList,
2025  fieldCount,
2026  fieldAcceptList,
2027  fieldRejectList,
2028  depth,
2029  (relativePathBase + fieldNode.first) + "/", // relativePathBase
2030  inFirstRecord // continue inFirstRecord (or not) depth search
2031  );
2032  } // end handle unique link node
2033  } // end field node loop
2034 } // end recursiveGetCommonFields()
2035 
2036 //==============================================================================
2037 // getChildrenByPriority
2038 // returns them in order encountered in the table
2039 // if filterMap criteria, then rejects any that do not meet all criteria
2040 //
2041 // value can be comma-separated for OR of multiple values
2042 std::vector<std::vector<std::pair<std::string, ConfigurationTree>>> ConfigurationTree::getChildrenByPriority(
2043  std::map<std::string /*relative-path*/, std::string /*value*/> filterMap, bool onlyStatusTrue) const
2044 {
2045  std::vector<std::vector<std::pair<std::string, ConfigurationTree>>> retVector;
2046 
2047  //__COUT__ << "Children of node: " << getValueAsString() << __E__;
2048 
2049  bool filtering = filterMap.size();
2050  bool skip;
2051  std::string fieldValue;
2052 
2053  bool createContainer;
2054 
2055  std::vector<std::vector<std::string>> childrenNamesByPriority = getChildrenNamesByPriority(onlyStatusTrue);
2056 
2057  for(auto& childNamesAtPriority : childrenNamesByPriority)
2058  {
2059  createContainer = true;
2060 
2061  for(auto& childName : childNamesAtPriority)
2062  {
2063  //__COUT__ << "\tChild: " << childName << __E__;
2064 
2065  if(filtering)
2066  {
2067  // if all criteria are not met, then skip
2068  skip = false;
2069 
2070  // for each filter, check value
2071  for(const auto& filterPair : filterMap)
2072  {
2073  std::string filterPath = childName + "/" + filterPair.first;
2074  __COUTV__(filterPath);
2075  try
2076  {
2077  // extract field value list
2078  std::vector<std::string> fieldValues;
2079  StringMacros::getVectorFromString(filterPair.second, fieldValues, std::set<char>({','}) /*delimiters*/);
2080 
2081  __COUTV__(fieldValues.size());
2082 
2083  skip = true;
2084  // for each field check if any match
2085  for(const auto& fieldValue : fieldValues)
2086  {
2087  // Note: that ConfigurationTree maps both fields associated
2088  // with a link to the same node instance. The behavior is
2089  // likely not expected as response for this function.. so for
2090  // links return actual value for field name specified i.e.
2091  // if Table of link is requested give that; if linkID is
2092  // requested give that. use TRUE in getValueAsString for
2093  // proper
2094  // behavior
2095 
2096  __COUT__ << "\t\tCheck: " << filterPair.first << " == " << fieldValue << " => " << StringMacros::decodeURIComponent(fieldValue)
2097  << " ??? " << this->getNode(filterPath).getValueAsString(true) << __E__;
2098 
2099  if(StringMacros::wildCardMatch(StringMacros::decodeURIComponent(fieldValue), this->getNode(filterPath).getValueAsString(true)))
2100  {
2101  // found a match for the field/value pair
2102  skip = false;
2103  break;
2104  }
2105  }
2106  }
2107  catch(...)
2108  {
2109  __SS__ << "Failed to access filter path '" << filterPath << "' - aborting." << __E__;
2110 
2111  ss << nodeDump() << __E__;
2112  __SS_THROW__;
2113  }
2114 
2115  if(skip)
2116  break; // no match for this field, so stop checking and skip this
2117  // record
2118  }
2119 
2120  if(skip)
2121  continue; // skip this record
2122 
2123  //__COUT__ << "\tChild accepted: " << childName << __E__;
2124  }
2125 
2126  if(createContainer)
2127  {
2128  retVector.push_back(std::vector<std::pair<std::string, ConfigurationTree>>());
2129  createContainer = false;
2130  }
2131 
2132  retVector[retVector.size() - 1].push_back(std::pair<std::string, ConfigurationTree>(childName, this->getNode(childName, true)));
2133  } // end children within priority loop
2134  } // end children by priority loop
2135 
2136  //__COUT__ << "Done w/Children of node: " << getValueAsString() << __E__;
2137  return retVector;
2138 } // end getChildrenByPriority()
2139 
2140 //==============================================================================
2141 // getChildren
2142 // returns them in order encountered in the table
2143 // if filterMap criteria, then rejects any that do not meet all criteria
2144 // filterMap-value can be comma-separated for OR of multiple values
2145 //
2146 // Note: filterMap is handled special for groupID fields
2147 // matches are considered after parsing | for set of groupIDs
2148 //
2149 std::vector<std::pair<std::string, ConfigurationTree>> ConfigurationTree::getChildren(std::map<std::string /*relative-path*/, std::string /*value*/> filterMap,
2150  bool byPriority,
2151  bool onlyStatusTrue) const
2152 {
2153  std::vector<std::pair<std::string, ConfigurationTree>> retVector;
2154 
2155  //__COUT__ << "Children of node: " << getValueAsString() << __E__;
2156 
2157  bool filtering = filterMap.size();
2158  bool skip;
2159  std::string fieldValue;
2160 
2161  std::vector<std::string> childrenNames = getChildrenNames(byPriority, onlyStatusTrue);
2162  for(auto& childName : childrenNames)
2163  {
2164  //__COUT__ << "\tChild: " << childName << __E__;
2165 
2166  if(filtering)
2167  {
2168  // if all criteria are not met, then skip
2169  skip = false;
2170 
2171  // for each filter, check value
2172  for(const auto& filterPair : filterMap)
2173  {
2174  std::string filterPath = childName + "/" + filterPair.first;
2175  __COUTV__(filterPath);
2176 
2177  ConfigurationTree childNode = this->getNode(filterPath);
2178  try
2179  {
2180  // extract field value list
2181  std::vector<std::string> fieldValues;
2182  StringMacros::getVectorFromString(filterPair.second, fieldValues, std::set<char>({','}) /*delimiters*/);
2183 
2184  __COUTV__(fieldValues.size());
2185 
2186  skip = true;
2187  // for each field check if any match
2188  for(const auto& fieldValue : fieldValues)
2189  {
2190  // Note: that ConfigurationTree maps both fields associated with a
2191  // link to the same node instance. The behavior is likely not
2192  // expected as response for this function.. so for links
2193  // return
2194  // actual value for field name specified i.e. if Table of link
2195  // is requested give that; if linkID is requested give that. use
2196  // TRUE in getValueAsString for proper behavior
2197 
2198  if(childNode.isGroupIDNode())
2199  {
2200  // handle groupID node special, check against set of groupIDs
2201 
2202  bool groupIdFound = false;
2203  std::set<std::string> setOfGroupIDs = childNode.getSetOfGroupIDs();
2204 
2205  for(auto& groupID : setOfGroupIDs)
2206  {
2207  __COUT__ << "\t\tGroupID Check: " << filterPair.first << " == " << fieldValue << " => "
2208  << StringMacros::decodeURIComponent(fieldValue) << " ??? " << groupID << __E__;
2209 
2210  if(StringMacros::wildCardMatch(StringMacros::decodeURIComponent(fieldValue), groupID))
2211  {
2212  // found a match for the field/groupId pair
2213  __COUT__ << "Found match" << __E__;
2214  groupIdFound = true;
2215  break;
2216  }
2217  } // end groupID search
2218 
2219  if(groupIdFound)
2220  {
2221  // found a match for the field/groupId-set pair
2222  __COUT__ << "Found break match" << __E__;
2223  skip = false;
2224  break;
2225  }
2226  }
2227  else // normal child node, check against value
2228  {
2229  __COUT__ << "\t\tCheck: " << filterPair.first << " == " << fieldValue << " => " << StringMacros::decodeURIComponent(fieldValue)
2230  << " ??? " << childNode.getValueAsString(true) << __E__;
2231 
2232  if(StringMacros::wildCardMatch(StringMacros::decodeURIComponent(fieldValue), childNode.getValueAsString(true)))
2233  {
2234  // found a match for the field/value pair
2235  skip = false;
2236  break;
2237  }
2238  }
2239  }
2240  }
2241  catch(...)
2242  {
2243  __SS__ << "Failed to access filter path '" << filterPath << "' - aborting." << __E__;
2244 
2245  ss << nodeDump() << __E__;
2246  __SS_THROW__;
2247  }
2248 
2249  if(skip)
2250  break; // no match for this field, so stop checking and skip this
2251  // record
2252  }
2253 
2254  if(skip)
2255  continue; // skip this record
2256 
2257  //__COUT__ << "\tChild accepted: " << childName << __E__;
2258  }
2259 
2260  retVector.push_back(std::pair<std::string, ConfigurationTree>(childName, this->getNode(childName, true)));
2261  }
2262 
2263  //__COUT__ << "Done w/Children of node: " << getValueAsString() << __E__;
2264  return retVector;
2265 } // end getChildren()
2266 
2267 //==============================================================================
2268 // getChildren
2269 // returns them in order encountered in the table
2270 std::map<std::string, ConfigurationTree> ConfigurationTree::getChildrenMap(void) const
2271 {
2272  std::map<std::string, ConfigurationTree> retMap;
2273 
2274  //__COUT__ << "Children of node: " << getValueAsString() << __E__;
2275 
2276  std::vector<std::string> childrenNames = getChildrenNames();
2277  for(auto& childName : childrenNames)
2278  {
2279  //__COUT__ << "\tChild: " << childName << __E__;
2280  retMap.insert(std::pair<std::string, ConfigurationTree>(childName, this->getNode(childName)));
2281  }
2282 
2283  //__COUT__ << "Done w/Children of node: " << getValueAsString() << __E__;
2284  return retMap;
2285 } // end getChildrenMap()
2286 
2287 //==============================================================================
2288 inline bool ConfigurationTree::isRootNode(void) const { return (!table_); }
2289 
2290 //==============================================================================
2291 inline bool ConfigurationTree::isTableNode(void) const { return (table_ && row_ == TableView::INVALID && col_ == TableView::INVALID); }
2292 
2293 //==============================================================================
2294 // same as status()
2295 bool ConfigurationTree::isEnabled(void) const
2296 {
2297  if(!isUIDNode())
2298  {
2299  __SS__ << "Can not get status of '" << getValueAsString() <<
2300  ".' Can only check the status of a UID/Record node!" << __E__;
2301  __SS_THROW__;
2302  }
2303 
2304  bool tmpStatus;
2305  tableView_->getValue(tmpStatus, row_, tableView_->getColStatus());
2306  return tmpStatus;
2307 } // end isEnabled()
2308 
2309 //==============================================================================
2310 bool ConfigurationTree::isStatusNode(void) const
2311 {
2312  if(!isValueNode())
2313  return false;
2314 
2315  return col_ == tableView_->getColStatus();
2316 } // end isStatusNode()
2317 
2318 //==============================================================================
2319 // getChildrenNamesByPriority
2320 // returns them in priority order encountered in the table
2321 std::vector<std::vector<std::string>> ConfigurationTree::getChildrenNamesByPriority(bool onlyStatusTrue) const
2322 {
2323  std::vector<std::vector<std::string /*child name*/>> retVector;
2324 
2325  if(!tableView_)
2326  {
2327  __SS__ << "Can not get children names of '" << getValueAsString() << "' with null configuration view pointer!" << __E__;
2328  if(isLinkNode() && isDisconnected())
2329  ss << " This node is a disconnected link to " << getDisconnectedTableName() << __E__;
2330 
2331  ss << nodeDump() << __E__;
2332  __SS_ONLY_THROW__;
2333  }
2334 
2335  if(row_ == TableView::INVALID && col_ == TableView::INVALID)
2336  {
2337  // this node is table node
2338  // so return all uid node strings that match groupId
2339 
2340  // bool tmpStatus;
2341 
2342  std::vector<std::vector<unsigned int /*group row*/>> groupRowsByPriority = tableView_->getGroupRowsByPriority(
2343  groupId_ == "" ? TableView::INVALID : // if no group ID, take all rows and ignore column, do not attempt link lookup
2344  tableView_->getLinkGroupIDColumn(childLinkIndex_),
2345  groupId_,
2346  onlyStatusTrue);
2347 
2348  // now build vector of vector names by priority
2349  for(const auto& priorityChildRowVector : groupRowsByPriority)
2350  {
2351  retVector.push_back(std::vector<std::string /*child name*/>());
2352  for(const auto& priorityChildRow : priorityChildRowVector)
2353  retVector[retVector.size() - 1].push_back(tableView_->getDataView()[priorityChildRow][tableView_->getColUID()]);
2354  }
2355 
2356  }
2357  else if(row_ == TableView::INVALID)
2358  {
2359  __SS__ << "Malformed ConfigurationTree" << __E__;
2360 
2361  ss << nodeDump() << __E__;
2362  __SS_THROW__;
2363  }
2364  else if(col_ == TableView::INVALID)
2365  {
2366  // this node is uid node
2367  // so return all link and value nodes
2368 
2369  for(unsigned int c = 0; c < tableView_->getNumberOfColumns(); ++c)
2370  if(c == tableView_->getColUID() || // skip UID and linkID columns (only show
2371  // link column, to avoid duplicates)
2372  tableView_->getColumnInfo(c).isChildLinkGroupID() || tableView_->getColumnInfo(c).isChildLinkUID())
2373  continue;
2374  else
2375  {
2376  retVector.push_back(std::vector<std::string /*child name*/>());
2377  retVector[retVector.size() - 1].push_back(tableView_->getColumnInfo(c).getName());
2378  }
2379  }
2380  else // this node is value node, so has no node to choose from
2381  {
2382  // this node is value node, cant go any deeper!
2383  __SS__ << "\n\nError occurred looking for children of nodeName=" << getValueName() << "\n\n"
2384  << "Invalid depth! getChildrenValues() called from a value point in the "
2385  "Configuration Tree."
2386  << __E__;
2387 
2388  ss << nodeDump() << __E__;
2389  __SS_THROW__;
2390  }
2391 
2392  return retVector;
2393 } // end getChildrenNamesByPriority()
2394 
2395 //==============================================================================
2396 // getChildrenNames
2397 // returns them in order encountered in the table
2398 std::vector<std::string> ConfigurationTree::getChildrenNames(bool byPriority, bool onlyStatusTrue) const
2399 {
2400  std::vector<std::string /*child name*/> retVector;
2401 
2402  if(!tableView_)
2403  {
2404  __SS__ << "Can not get children names of '" << getValueAsString() << "' with null configuration view pointer!" << __E__;
2405  if(isLinkNode() && isDisconnected())
2406  ss << " This node is a disconnected link to " << getDisconnectedTableName() << __E__;
2407  __SS_ONLY_THROW__;
2408  }
2409 
2410  if(row_ == TableView::INVALID && col_ == TableView::INVALID)
2411  {
2412  // this node is table node
2413  // so return all uid node strings that match groupId
2414  std::vector<unsigned int /*group row*/> groupRows =
2415  tableView_->getGroupRows((groupId_ == "" ? TableView::INVALID : // if no group ID, take all rows, do not attempt link lookup
2416  tableView_->getLinkGroupIDColumn(childLinkIndex_)),
2417  groupId_,
2418  onlyStatusTrue,
2419  byPriority);
2420 
2421  // now build vector of vector names by priority
2422  for(const auto& groupRow : groupRows)
2423  retVector.push_back(tableView_->getDataView()[groupRow][tableView_->getColUID()]);
2424 
2425  // bool tmpStatus;
2426  //
2427  // if(byPriority) // reshuffle by priority
2428  // {
2429  // try
2430  // {
2431  // std::map<uint64_t /*priority*/, std::vector<unsigned int /*child row*/>> orderedByPriority;
2432  // std::vector<std::string /*child name*/> retPrioritySet;
2433  //
2434  // unsigned int col = tableView_->getColPriority();
2435  //
2436  // uint64_t tmpPriority;
2437  //
2438  // for(unsigned int r = 0; r < tableView_->getNumberOfRows(); ++r)
2439  // if(groupId_ == "" || tableView_->isEntryInGroup(r, childLinkIndex_, groupId_))
2440  // {
2441  // // check status if needed
2442  // if(onlyStatusTrue)
2443  // {
2444  // tableView_->getValue(tmpStatus, r, tableView_->getColStatus());
2445  //
2446  // if(!tmpStatus)
2447  // continue; // skip those with status false
2448  // }
2449  //
2450  // tableView_->getValue(tmpPriority, r, col);
2451  // // do not accept DEFAULT value of 0.. convert to 100
2452  // orderedByPriority[tmpPriority ? tmpPriority : 100].push_back(r);
2453  // }
2454  //
2455  // // at this point have priority map
2456  // // now build return vector
2457  //
2458  // for(const auto& priorityChildRowVector : orderedByPriority)
2459  // for(const auto& priorityChildRow : priorityChildRowVector.second)
2460  // retVector.push_back(tableView_->getDataView()[priorityChildRow][tableView_->getColUID()]);
2461  //
2462  // __COUT__ << "Returning priority children list." << __E__;
2463  // return retVector;
2464  // }
2465  // catch(std::runtime_error& e)
2466  // {
2467  // __COUT_WARN__ << "Priority configuration not found. Assuming all "
2468  // "children have equal priority. "
2469  // << __E__;
2470  // retVector.clear();
2471  // }
2472  // }
2473  // // else not by priority
2474  //
2475  // for(unsigned int r = 0; r < tableView_->getNumberOfRows(); ++r)
2476  // if(groupId_ == "" || tableView_->isEntryInGroup(r, childLinkIndex_, groupId_))
2477  // {
2478  // // check status if needed
2479  // if(onlyStatusTrue)
2480  // {
2481  // tableView_->getValue(tmpStatus, r, tableView_->getColStatus());
2482  //
2483  // if(!tmpStatus)
2484  // continue; // skip those with status false
2485  // }
2486  //
2487  // retVector.push_back(tableView_->getDataView()[r][tableView_->getColUID()]);
2488  // }
2489  }
2490  else if(row_ == TableView::INVALID)
2491  {
2492  __SS__ << "Malformed ConfigurationTree" << __E__;
2493 
2494  ss << nodeDump() << __E__;
2495  __SS_THROW__;
2496  }
2497  else if(col_ == TableView::INVALID)
2498  {
2499  // this node is uid node
2500  // so return all link and value nodes
2501 
2502  for(unsigned int c = 0; c < tableView_->getNumberOfColumns(); ++c)
2503  if(c == tableView_->getColUID() || // skip UID and linkID columns (only show
2504  // link column, to avoid duplicates)
2505  tableView_->getColumnInfo(c).isChildLinkGroupID() || tableView_->getColumnInfo(c).isChildLinkUID())
2506  continue;
2507  else
2508  retVector.push_back(tableView_->getColumnInfo(c).getName());
2509  }
2510  else // this node is value node, so has no node to choose from
2511  {
2512  // this node is value node, cant go any deeper!
2513  __SS__ << "\n\nError occurred looking for children of nodeName=" << getValueName() << "\n\n"
2514  << "Invalid depth! getChildrenValues() called from a value point in the "
2515  "Configuration Tree."
2516  << __E__;
2517 
2518  ss << nodeDump() << __E__;
2519  __SS_THROW__;
2520  }
2521 
2522  return retVector;
2523 } // end getChildrenNames()
2524 
2525 //==============================================================================
2526 // getValueAsTreeNode
2527 // returns tree node for value of this node, treating the value
2528 // as a string for the absolute path string from root of tree
2529 ConfigurationTree ConfigurationTree::getValueAsTreeNode(void) const
2530 {
2531  // check if first character is a /, .. if so try to get value in tree
2532  // if exception, just take value
2533  // note: this call will throw an error, in effect, if not a "value" node
2534  if(!tableView_)
2535  {
2536  __SS__ << "Invalid node for get value." << __E__;
2537  __SS_THROW__;
2538  }
2539 
2540  std::string valueString = tableView_->getValueAsString(row_, col_, true /* convertEnvironmentVariables */);
2541  //__COUT__ << valueString << __E__;
2542  if(valueString.size() && valueString[0] == '/')
2543  {
2544  //__COUT__ << "Starts with '/' - check if valid tree path: " << valueString <<
2545  // __E__;
2546  try
2547  {
2548  ConfigurationTree retNode = configMgr_->getNode(valueString);
2549  __COUT__ << "Found a valid tree path in value!" << __E__;
2550  return retNode;
2551  }
2552  catch(...)
2553  {
2554  __SS__ << "Invalid tree path." << __E__;
2555  __SS_ONLY_THROW__;
2556  }
2557  }
2558 
2559  {
2560  __SS__ << "Invalid value string '" << valueString << "' - must start with a '/' character." << __E__;
2561  __SS_ONLY_THROW__;
2562  }
2563 } // end getValueAsTreeNode()