Line data Source code
1 : /** \file indiCompRuleConfig.hpp
2 : * \brief Configuration of rules for the MagAO-X stateRuleEngine
3 : *
4 : * \ingroup stateRuleEngine_files
5 : */
6 :
7 : #ifndef stateRuleEngine_indiCompRuleConfig_hpp
8 : #define stateRuleEngine_indiCompRuleConfig_hpp
9 :
10 : #include <map>
11 :
12 : #include "indiCompRules.hpp"
13 :
14 : /// Structure to provide management of the rule and property maps
15 : /** This owns all pointers in the rule engine, and `delete`s them on destruction.
16 : */
17 : struct indiRuleMaps
18 : {
19 : typedef std::map<std::string, indiCompRule*> ruleMapT;
20 : typedef std::map<std::string, pcf::IndiProperty*> propMapT;
21 :
22 : ruleMapT rules;
23 : propMapT props;
24 :
25 0 : ~indiRuleMaps()
26 : {
27 0 : auto rit = rules.begin();
28 0 : while(rit != rules.end())
29 : {
30 0 : delete rit->second;
31 0 : ++rit;
32 : }
33 :
34 0 : auto pit = props.begin();
35 0 : while(pit != props.end())
36 : {
37 0 : delete pit->second;
38 0 : ++pit;
39 : }
40 0 : }
41 : };
42 :
43 : /* Structure used to hold ruleVal rule keys aside for final processing
44 : ruleVal rules can be created before the rules they link exist, so
45 : we hold the keys aside and set the pointers after all rules are created.
46 : */
47 : struct ruleRuleKeys
48 : {
49 : std::string rule1;
50 : std::string rule2;
51 : };
52 :
53 : /// Extract a property from a rule configuration
54 : /** Reads the property and element, adding the property to the property map if necessary.
55 : *
56 : * \throws mx::err::invalidconfig if the property is already in the map but of a different type
57 : */
58 0 : void extractRuleProp( pcf::IndiProperty ** prop, ///< [out] pointer to the property, newly created or existing, which is in the map.
59 : std::string & element, ///< [out] the element name from the configuration
60 : indiRuleMaps & maps, ///< [in] contains the property map to which the property is added
61 : const std::string & section, ///< [in] name of the section for this rule
62 : const std::string & propkey, ///< [in] the key for the property name
63 : const std::string & elkey, ///< [in] the key for the element name
64 : const pcf::IndiProperty::Type & type, ///< [in] the type of the property
65 : mx::app::appConfigurator & config ///< [in] the application configuration structure
66 : )
67 : {
68 0 : std::string property;
69 0 : config.configUnused(property, mx::app::iniFile::makeKey(section, propkey ));
70 :
71 0 : if(maps.props.count(property) > 0)
72 : {
73 : //If the property already exists we just check if it's the right type
74 0 : if(maps.props[property]->getType() != type)
75 : {
76 0 : mxThrowException(mx::err::invalidconfig, "extracPropRule", "property " + property + " exists but is not correct type");
77 : }
78 :
79 0 : *prop = maps.props[property];
80 : }
81 : else
82 : {
83 : //Otherwise we create it
84 0 : *prop = new pcf::IndiProperty(type);
85 0 : maps.props.insert(std::pair<std::string, pcf::IndiProperty*>({property, *prop}));
86 :
87 : ///\todo have to split device and propertyName
88 : }
89 :
90 0 : config.configUnused(element, mx::app::iniFile::makeKey(section, elkey));
91 :
92 0 : }
93 :
94 : /// \cond
95 : // strip leading and trailing whitespace and then opening and closing "". leaves spaces between "".
96 0 : inline void stripQuotesWS( std::string &str )
97 : {
98 0 : if( str.size() == 0 )
99 : {
100 0 : return;
101 : }
102 :
103 0 : if( str[0] != '\"' && str[0] != ' ' && str.back() != ' ' ) // get out fast if we can
104 : {
105 0 : return;
106 : }
107 :
108 : // strip white space at front
109 0 : size_t ns = str.find_first_not_of( " \t\r\n" );
110 0 : if( ns != std::string::npos && ns != 0 )
111 : {
112 0 : str.erase( 0, ns );
113 :
114 0 : if( str.size() == 0 )
115 : {
116 0 : return;
117 : }
118 : }
119 0 : else if( ns == std::string::npos ) // the rare all spaces
120 : {
121 0 : str = "";
122 0 : return;
123 : }
124 :
125 : // strip white space at back
126 0 : ns = str.find_last_not_of( " \t\r\n" );
127 0 : if( ns != std::string::npos && ns != str.size() - 1 )
128 : {
129 0 : str.erase( ns + 1 );
130 :
131 0 : if( str.size() == 0 )
132 : {
133 0 : return;
134 : }
135 : }
136 :
137 0 : if( str[0] == '\"' && str.back() == '\"' )
138 : {
139 0 : if( str.size() == 1 || str.size() == 2 )
140 : {
141 0 : str = "";
142 0 : return;
143 : }
144 0 : str.erase( str.size() - 1, 1 );
145 0 : str.erase( 0, 1 );
146 : }
147 0 : else if( str[0] == '\"')
148 : {
149 0 : if( str.size() == 1 )
150 : {
151 0 : str = "";
152 0 : return;
153 : }
154 0 : str.erase( 0, 1 );
155 : }
156 0 : else if( str.back() == '\"' )
157 : {
158 0 : if( str.size() == 1 || str.size() == 2 )
159 : {
160 0 : str = "";
161 0 : return;
162 : }
163 0 : str.erase( str.size() - 1, 1 );
164 : }
165 : }
166 : /// \endcond
167 :
168 :
169 : /// Load the rule and properties maps for a rule engine from a configuration file
170 : /** ///\todo check for insertion failure
171 : * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
172 : */
173 0 : void loadRuleConfig( indiRuleMaps & maps, ///< [out] contains the rule and property maps in which to place the items found in config
174 : std::map<std::string, ruleRuleKeys> & rrkMap, ///< [out] Holds the ruleVal rule keys aside for later post-processing
175 : mx::app::appConfigurator & config ///< [in] the application configuration structure
176 : )
177 : {
178 0 : std::vector<std::string> sections;
179 :
180 0 : config.unusedSections(sections);
181 :
182 0 : if( sections.size() == 0 )
183 : {
184 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "no rules found in config");
185 : }
186 :
187 :
188 0 : for(size_t i=0; i< sections.size(); ++i)
189 : {
190 0 : bool ruleTypeSet = config.isSetUnused(mx::app::iniFile::makeKey(sections[i], "ruleType" ));
191 :
192 : //If there is no ruleType then this isn't a rule
193 0 : if( !ruleTypeSet ) continue;
194 :
195 : //If the rule already exists this is an error
196 0 : if(maps.rules.count(sections[i]) != 0)
197 : {
198 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "duplicate rule: " + sections[i]);
199 : }
200 :
201 0 : std::string ruleType;
202 0 : config.configUnused(ruleType, mx::app::iniFile::makeKey(sections[i], "ruleType" ));
203 :
204 0 : std::string priostr="none";
205 0 : config.configUnused(priostr, mx::app::iniFile::makeKey(sections[i], "priority" ));
206 0 : rulePriority priority = string2priority(priostr);
207 :
208 0 : std::string message;
209 0 : config.configUnused(message, mx::app::iniFile::makeKey(sections[i], "message" ));
210 0 : stripQuotesWS(message); //strips "" and any leading/trailing whitespace
211 :
212 :
213 :
214 0 : std::string compstr="Eq";
215 0 : config.configUnused(compstr, mx::app::iniFile::makeKey(sections[i], "comp" ));
216 0 : ruleComparison comparison = string2comp(compstr);
217 :
218 0 : if(ruleType == numValRule::name)
219 : {
220 0 : numValRule * nvr = new numValRule;
221 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], nvr}));
222 :
223 0 : nvr->priority(priority);
224 0 : nvr->message(message);
225 0 : nvr->comparison(comparison);
226 :
227 0 : pcf::IndiProperty * prop = nullptr;
228 0 : std::string element;
229 :
230 0 : extractRuleProp( &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Number, config );
231 0 : nvr->property(prop);
232 0 : nvr->element(element);
233 :
234 0 : double target = nvr->target();
235 0 : config.configUnused(target, mx::app::iniFile::makeKey(sections[i], "target" ));
236 0 : nvr->target(target);
237 :
238 0 : double tol = nvr->tol();
239 0 : config.configUnused(tol, mx::app::iniFile::makeKey(sections[i], "tol" ));
240 0 : nvr->tol(tol);
241 0 : }
242 0 : else if(ruleType == txtValRule::name)
243 : {
244 0 : txtValRule * tvr = new txtValRule;
245 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], tvr}));
246 :
247 0 : tvr->priority(priority);
248 0 : tvr->message(message);
249 0 : tvr->comparison(comparison);
250 :
251 0 : pcf::IndiProperty * prop = nullptr;
252 0 : std::string element;
253 :
254 0 : extractRuleProp( &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Text, config );
255 0 : tvr->property(prop);
256 0 : tvr->element(element);
257 :
258 :
259 0 : std::string target = tvr->target();
260 0 : config.configUnused(target, mx::app::iniFile::makeKey(sections[i], "target" ));
261 0 : tvr->target(target);
262 :
263 0 : }
264 0 : else if(ruleType == swValRule::name)
265 : {
266 0 : swValRule * svr = new swValRule;
267 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], svr}));
268 :
269 0 : svr->priority(priority);
270 0 : svr->message(message);
271 0 : svr->comparison(comparison);
272 :
273 0 : pcf::IndiProperty * prop = nullptr;
274 0 : std::string element;
275 :
276 0 : extractRuleProp( &prop, element, maps, sections[i], "property", "element", pcf::IndiProperty::Switch, config );
277 0 : svr->property(prop);
278 0 : svr->element(element);
279 :
280 0 : std::string target = "On";
281 0 : config.configUnused(target, mx::app::iniFile::makeKey(sections[i], "target" ));
282 0 : svr->target(target);
283 0 : }
284 0 : else if(ruleType == elCompNumRule::name)
285 : {
286 0 : elCompNumRule * nvr = new elCompNumRule;
287 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], nvr}));
288 :
289 0 : nvr->priority(priority);
290 0 : nvr->message(message);
291 0 : nvr->comparison(comparison);
292 :
293 : pcf::IndiProperty * prop1;
294 0 : std::string element1;
295 :
296 0 : extractRuleProp( &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Number, config );
297 0 : nvr->property1(prop1);
298 0 : nvr->element1(element1);
299 :
300 : pcf::IndiProperty * prop2;
301 0 : std::string element2;
302 :
303 0 : extractRuleProp( &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Number, config );
304 0 : nvr->property2(prop2);
305 0 : nvr->element2(element2);
306 0 : }
307 0 : else if(ruleType == elCompTxtRule::name)
308 : {
309 0 : elCompTxtRule * tvr = new elCompTxtRule;
310 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], tvr}));
311 :
312 0 : tvr->priority(priority);
313 0 : tvr->message(message);
314 0 : tvr->comparison(comparison);
315 :
316 : pcf::IndiProperty * prop1;
317 0 : std::string element1;
318 :
319 0 : extractRuleProp( &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Text, config );
320 0 : tvr->property1(prop1);
321 0 : tvr->element1(element1);
322 :
323 : pcf::IndiProperty * prop2;
324 0 : std::string element2;
325 :
326 0 : extractRuleProp( &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Text, config );
327 0 : tvr->property2(prop2);
328 0 : tvr->element2(element2);
329 0 : }
330 0 : else if(ruleType == elCompSwRule::name)
331 : {
332 0 : elCompSwRule * svr = new elCompSwRule;
333 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], svr}));
334 :
335 0 : svr->priority(priority);
336 0 : svr->message(message);
337 0 : svr->comparison(comparison);
338 :
339 : pcf::IndiProperty * prop1;
340 0 : std::string element1;
341 :
342 0 : extractRuleProp( &prop1, element1, maps, sections[i], "property1", "element1", pcf::IndiProperty::Switch, config );
343 0 : svr->property1(prop1);
344 0 : svr->element1(element1);
345 :
346 : pcf::IndiProperty * prop2;
347 0 : std::string element2;
348 :
349 0 : extractRuleProp( &prop2, element2, maps, sections[i], "property2", "element2", pcf::IndiProperty::Switch, config );
350 0 : svr->property2(prop2);
351 0 : svr->element2(element2);
352 0 : }
353 0 : else if(ruleType == ruleCompRule::name)
354 : {
355 : //Here we have to hold the ruleVal keys separately for later processing after all the rules are created.
356 :
357 0 : if(rrkMap.count(sections[i]) > 0)
358 : {
359 : //This probably should be impossible, since we already checked maps.rules above...
360 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "duplicate ruleRule: " + sections[i]);
361 : }
362 :
363 0 : ruleCompRule * rcr = new ruleCompRule;
364 0 : maps.rules.insert(std::pair<std::string, indiCompRule*>({sections[i], rcr}));
365 :
366 0 : rcr->priority(priority);
367 0 : rcr->comparison(comparison);
368 :
369 0 : ruleRuleKeys rrk;
370 :
371 0 : config.configUnused(rrk.rule1, mx::app::iniFile::makeKey(sections[i], "rule1" ));
372 0 : if(rrk.rule1 == "")
373 : {
374 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule1 for ruleVal rule " + sections[i] + " not found");
375 : }
376 0 : if(rrk.rule1 == sections[i])
377 : {
378 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule1 for ruleVal rule " + sections[i] + " can't equal rule name");
379 : }
380 :
381 0 : config.configUnused(rrk.rule2, mx::app::iniFile::makeKey(sections[i], "rule2" ));
382 0 : if(rrk.rule2 == "")
383 : {
384 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule2 for ruleVal rule " + sections[i] + " not found");
385 : }
386 0 : if(rrk.rule2 == sections[i])
387 : {
388 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule2 for ruleVal rule " + sections[i] + " can't equal rule name");
389 : }
390 :
391 0 : rrkMap.insert(std::pair<std::string, ruleRuleKeys>(sections[i], rrk));
392 0 : }
393 : else
394 : {
395 0 : mxThrowException(mx::err::notimpl, "loadRuleConfig", "unknown rule type " + ruleType + " in " + sections[i]);
396 : }
397 0 : }
398 0 : }
399 :
400 : /// Finalize ruleVal rules
401 : /** ///\todo check for insertion failure
402 : * ///\todo add a constructor that has priority, message, and comparison, to reduce duplication
403 : */
404 0 : void finalizeRuleValRules( indiRuleMaps & maps, /**< [in/out] contains the rule and property maps with rules ot finalize */
405 : std::map<std::string, ruleRuleKeys> & rrkMap ///< [out] Holds the ruleVal rule keys aside for later post-processing
406 : )
407 : {
408 : //Now set the rule pointers for any ruleVal rules
409 0 : auto it=rrkMap.begin();
410 0 : while(it != rrkMap.end())
411 : {
412 0 : if( maps.rules.count(it->first) == 0 )
413 : {
414 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule parsing error for " + it->first);
415 : }
416 :
417 0 : if( maps.rules.count(it->second.rule1) == 0 )
418 : {
419 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule1 " + it->second.rule1 + " not found for ruleVal rule " + it->first );
420 : }
421 :
422 0 : if( maps.rules.count(it->second.rule2) == 0 )
423 : {
424 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "rule2 " + it->second.rule2 + " not found for ruleVal rule " + it->first );
425 : }
426 :
427 0 : ruleCompRule * rcr = nullptr;
428 :
429 : try
430 : {
431 0 : rcr = dynamic_cast<ruleCompRule *>(maps.rules[it->first]);
432 : }
433 0 : catch(const std::exception & e)
434 : {
435 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", "error casting " + it->first + ": " + e.what() );
436 0 : }
437 :
438 0 : if(rcr == nullptr)
439 : {
440 0 : mxThrowException(mx::err::invalidconfig, "loadRuleConfig", it->first + " is not a ruleVal rule but has rules" );
441 : }
442 :
443 0 : rcr->rule1(maps.rules[it->second.rule1]);
444 0 : rcr->rule2(maps.rules[it->second.rule2]);
445 :
446 0 : ++it;
447 : }
448 0 : }
449 :
450 : #endif //stateRuleEngine_indiCompRuleConfig_hpp
|