1 /* _________________________________________________________________________
2 *
3 * UTILIB: A utility library for developing portable C++ codes.
4 * Copyright (c) 2008 Sandia Corporation.
5 * This software is distributed under the BSD License.
6 * Under the terms of Contract DE-AC04-94AL85000 with Sandia Corporation,
7 * the U.S. Government retains certain rights in this software.
8 * For more information, see the README file in the top UTILIB directory.
9 * _________________________________________________________________________
10 */
11
12 #include <utilib/PropertyDict.h>
13
14 using std::list;
15 using std::set;
16 using std::map;
17 using std::pair;
18
19 using std::cerr;
20 using std::endl;
21
22 #define ERR_HANDLER(MSG) \
23 if ( data->exceptionOnReplicationError ) \
24 EXCEPTION_MNGR( utilib::propertyDict_error, MSG ); \
25 else \
26 cerr << "WARNING: " << MSG << endl
27
28
29 namespace utilib {
30
31 //----------------------------------------------------------------------
32 // Property Store definitions
33 //
34
35 class PropertyDict::PropertyStore_property
36 : public PropertyDict::PropertyStore
37 {
38 public:
PropertyStore_property()39 PropertyStore_property()
40 : PropertyStore(m_property, Any(), NULL, ""),
41 m_property()
42 {}
43
PropertyStore_property(Property & property_,Any category_,Data * promote_,string description_)44 PropertyStore_property( Property& property_,
45 Any category_,
46 Data* promote_,
47 string description_ )
48 : PropertyStore(m_property, category_, promote_, description_),
49 m_property(property_)
50 {}
51
~PropertyStore_property()52 virtual ~PropertyStore_property() {}
53
privileged()54 virtual Privileged_Property* privileged()
55 { return NULL; }
56
57 private:
58 Property m_property;
59 };
60
61
62 class PropertyDict::PropertyStore_privileged
63 : public PropertyDict::PropertyStore
64 {
65 public:
PropertyStore_privileged()66 PropertyStore_privileged()
67 : PropertyStore(m_property, Any(), NULL, ""),
68 m_property()
69 {}
70
PropertyStore_privileged(Privileged_Property & property_,Any category_,Data * promote_,string description_)71 PropertyStore_privileged( Privileged_Property& property_,
72 Any category_,
73 Data* promote_,
74 string description_ )
75 : PropertyStore(m_property, category_, promote_, description_),
76 m_property(property_)
77 {}
78
~PropertyStore_privileged()79 virtual ~PropertyStore_privileged() {}
80
privileged()81 virtual Privileged_Property* privileged()
82 { return &m_property; }
83
84 private:
85 Privileged_Property m_property;
86 };
87
88
89
90 //----------------------------------------------------------------------
91 // Common PropertyDict writers
92 //
93
94 const std::string PropertyDict::Writer::DEFAULT_INDENT = " ";
95
96 void
97 PropertyDict::DescriptionWriter::
item(const Property &,const std::string & name,const std::string & description,bool)98 item( const Property& /*prop*/, const std::string& name,
99 const std::string& description, bool /*promoted*/ )
100 {
101 if ( key_width > 0 )
102 {
103 std::ios_base::fmtflags orig = os.flags() & std::ios_base::adjustfield;
104 os << indent
105 << std::left << std::setw(key_width) << name
106 << std::left;
107
108 string base = string(indent.size() + key_width + 2, ' ' );
109 string subindent = "";
110 if ( name.size() > static_cast<size_t>(key_width) )
111 os << std::endl << string(indent.size() + key_width, ' ' );
112
113 os << ": ";
114 size_t len = base.size();
115 string::size_type index = 0;
116 string::size_type space = 0;
117 do {
118 space = description.find_first_of(" \t\n", index);
119 size_t fragLen =
120 ( space == string::npos ? description.size() : space ) - index;
121 if ( len + fragLen > line_wrap )
122 {
123 os << std::endl << base << subindent;
124 len = base.size() + subindent.size();
125 }
126 os << description.substr(index, fragLen);
127 len += fragLen;
128 if ( space != string::npos )
129 {
130 if ( description[space] == '\n' )
131 {
132 ++space;
133 subindent = "";
134 while ( space < description.size() &&
135 (description[space]==' ' || description[space]=='\t') )
136 subindent += description[space++];
137 os << std::endl << base << subindent;
138 len = base.size() + subindent.size();
139 }
140 else
141 {
142 os << description[space];
143 ++len;
144 ++space;
145 }
146 }
147 index = space;
148 } while ( index != string::npos );
149 os << endl;
150 os.setf(orig, std::ios_base::adjustfield);
151 }
152 else
153 {
154 int len = -1*static_cast<int>(name.size());
155 if ( len < key_width )
156 {
157 key_width = len;
158 if ( max_key_width && key_width < max_key_width )
159 key_width = max_key_width;
160 }
161 }
162 }
163
164
165 void
166 PropertyDict::DescriptionWriter::
end(const PropertyDict * pd)167 end( const PropertyDict* pd )
168 {
169 if ( key_width < 0 )
170 pd->write( DescriptionWriter( os, indent, line_wrap,
171 max_key_width, -1*key_width ) );
172 }
173
174
175 void
176 PropertyDict::ValueWriter::
item(const Property & prop,const std::string & name,const std::string &,bool)177 item( const Property& prop, const std::string& name,
178 const std::string& /*description*/, bool /*promoted*/ )
179 {
180 if ( key_width > 0 )
181 {
182 std::ios_base::fmtflags orig = os.flags() & std::ios_base::adjustfield;
183 os << indent
184 << std::left << std::setw(key_width) << name
185 << ": "
186 << std::left << prop << endl;
187 os.setf(orig, std::ios_base::adjustfield);
188 }
189 else
190 {
191 int len = -1*static_cast<int>(name.size());
192 if ( len < key_width )
193 {
194 key_width = len;
195 if ( max_key_width && key_width < max_key_width )
196 key_width = max_key_width;
197 }
198 }
199 }
200
201
202 void
203 PropertyDict::ValueWriter::
end(const PropertyDict * pd)204 end( const PropertyDict* pd )
205 {
206 if ( key_width < 0 )
207 pd->write(ValueWriter(os, indent, max_key_width, -1*key_width));
208 }
209
210 //----------------------------------------------------------------------
211 // The PropertyDict Data members
212 //
213
~Data()214 PropertyDict::Data::~Data()
215 {
216 while ( ! data_sources.empty() )
217 {
218 (*data_sources.begin())->data_sinks.erase(this);
219 data_sources.erase(data_sources.begin());
220 }
221
222 set<Data*>::iterator r_it = data_sinks.begin();
223 set<Data*>::iterator r_itEnd = data_sinks.end();
224
225 while ( ! properties.empty() )
226 erase_impl(properties.begin());
227
228 while ( ! data_sinks.empty() )
229 {
230 (*data_sinks.begin())->data_sources.erase(this);
231 data_sinks.erase(data_sinks.begin());
232 }
233 }
234
235 void
236 PropertyDict::Data::
erase(const std::string name)237 erase(const std::string name)
238 {
239 propertyDict_t::iterator it = lookup(name);
240 if ( it == properties.end() )
241 EXCEPTION_MNGR(propertyDict_error, "PropertyDict::erase(): "
242 "attempt to erase a nonexistent Property '"
243 << name << "'");
244 erase_impl(it);
245 }
246
247 void
248 PropertyDict::Data::
erase_impl(propertyDict_t::iterator it)249 erase_impl(propertyDict_t::iterator it)
250 {
251 if ( it->second->promote && ! data_sinks.empty() )
252 {
253 set<Data*>::iterator r_it = data_sinks.begin();
254 set<Data*>::iterator r_itEnd = data_sinks.end();
255 for( ; r_it != r_itEnd; ++r_it )
256 (*r_it)->erase_promoted(it);
257 }
258
259 delete it->second;
260 properties.erase(it);
261 }
262
263 void
264 PropertyDict::Data::
erase_promoted(propertyDict_t::iterator & source)265 erase_promoted(propertyDict_t::iterator& source)
266 {
267 propertyDict_t::iterator it = properties.find(source->first);
268 if ( it == properties.end() )
269 return;
270 if ( it->second->promote != source->second->promote )
271 return;
272
273 erase_impl(it);
274 }
275
276
277 PropertyDict::propertyDict_t::iterator
278 PropertyDict::Data::
declare_impl(const std::string & name,PropertyStore * store)279 declare_impl( const std::string &name, PropertyStore *store )
280 {
281 string normalized_name = normalizeKeys ? normalize(name) : name;
282 pair<propertyDict_t::iterator, bool> inserted =
283 properties.insert(make_pair(normalized_name, store));
284 if ( ! inserted.second )
285 {
286 delete store;
287 EXCEPTION_MNGR(propertyDict_error,
288 "PropertyDict::declare(): "
289 "attempt to declare duplicate Property '"
290 << name << "'");
291 }
292 store->id = ++max_propertyStore_id;
293
294 if ( store->promote && ! data_sinks.empty() )
295 {
296 set<Data*>::iterator r_it = data_sinks.begin();
297 set<Data*>::iterator r_itEnd = data_sinks.end();
298 try {
299 for( ; r_it != r_itEnd; ++r_it )
300 (*r_it)->declare_impl
301 ( normalized_name, new PropertyStore_property
302 ( store->property, store->category, store->promote,
303 store->description ) );
304 } catch ( propertyDict_error& e ) {
305 // unwind all declarations
306 while ( r_it != data_sinks.begin() )
307 {
308 --r_it;
309 (*r_it)->erase( normalized_name );
310 }
311 properties.erase(normalized_name);
312 delete store;
313 throw;
314 }
315 }
316
317 return inserted.first;
318 }
319
320 void
dereference_impl(Data & source)321 PropertyDict::Data::dereference_impl( Data &source )
322 {
323 #ifndef UTILIB_HAVE_BOOST
324 EXCEPTION_MNGR
325 ( propertyDict_error, "PropertyDict::dereference() is not "
326 "available when UTILIB is compiled without Boost support." );
327 #else
328 if ( ! data_sources.erase(&source) )
329 EXCEPTION_MNGR( propertyDict_error, "PropertyDict::dereference(): "
330 "specified source not found in reference_sources()" );
331 source.data_sinks.erase(this);
332
333 propertyDict_t::iterator p;
334 propertyDict_t::iterator prop = source.properties.begin();
335 propertyDict_t::iterator propEnd = source.properties.end();
336 for( ; prop != propEnd; ++prop)
337 {
338 if ( prop->second->promote )
339 erase_promoted(prop);
340 else if ( ( p = properties.find(prop->first) ) != properties.end() )
341 {
342 // NB: This WILL cause problems if there are 2 sources with the
343 // same property name. While this situation usually results in
344 // an exception, clearing exceptionOnReplicationError will only
345 // throw a warning...
346 p->second->source.disconnect();
347 }
348 }
349 #endif
350 }
351
352 void
dereference_all()353 PropertyDict::Data::dereference_all( )
354 {
355 while ( ! data_sources.empty() )
356 dereference_impl( **(data_sources.begin()) );
357 }
358
359
360 //----------------------------------------------------------------------
361 // The PropertyDict class members
362 //
363
~PropertyDict()364 PropertyDict::~PropertyDict()
365 {}
366
367
368 Property&
369 PropertyDict::
declare(const std::string name,Property property,Any category,bool promote,std::string description)370 declare( const std::string name, Property property,
371 Any category, bool promote, std::string description )
372 {
373 return data->declare_impl
374 ( name, new PropertyStore_property
375 ( property, category, promote ? &*this->data : NULL, description )
376 )->second->property;
377 }
378
379
380 Privileged_Property&
381 PropertyDict::
declare(const std::string name,Privileged_Property property,Any category,bool promote,std::string description)382 declare( const std::string name, Privileged_Property property,
383 Any category, bool promote, std::string description )
384 {
385 return *data->declare_impl
386 ( name, new PropertyStore_privileged
387 ( property, category, promote ? &*this->data : NULL, description )
388 )->second->privileged();
389 }
390
391
392
393 PropertyDict::propertyDict_t::iterator
get_impl(const std::string & name)394 PropertyDict::get_impl(const std::string& name)
395 {
396 propertyDict_t::iterator it = data->lookup(name);
397 if ( it == data->properties.end() )
398 {
399 if ( data->implicitDeclareIfDNE )
400 return data->declare_impl( name, new PropertyStore_privileged());
401
402 EXCEPTION_MNGR(propertyDict_error, "PropertyDict::get_impl(): "
403 "attempt to retrieve nonexistent Property '"
404 << name << "'");
405 }
406
407 return it;
408 }
409
410 PropertyDict::propertyDict_t::const_iterator
get_impl(const std::string & name) const411 PropertyDict::get_impl(const std::string& name) const
412 {
413 propertyDict_t::const_iterator it = data->lookup(name);
414 if ( it == data->properties.end() )
415 EXCEPTION_MNGR(propertyDict_error, "PropertyDict::get_impl() const: "
416 "attempt to retrieve nonexistent Property '"
417 << name << "'");
418
419 return it;
420 }
421
422
423
424 const int PropertyDict::connection_group = -1000;
425
426 /** NB: This method has side-effects: any properties that are set to
427 * reference the source PropertyDict will have their value reset to
428 * that of the source PropertyDict (therefore calling any
429 * locally-registered onChange() callbacks!)
430 */
431 void
432 PropertyDict::
reference(PropertyDict & source,std::set<Any> exclude,std::set<std::string> block_promotion)433 reference( PropertyDict &source,
434 std::set<Any> exclude,
435 std::set<std::string> block_promotion )
436 {
437 #ifndef UTILIB_HAVE_BOOST
438 EXCEPTION_MNGR
439 ( propertyDict_error, "PropertyDict::reference() is not "
440 "available when UTILIB is compiled without Boost support." );
441 #else
442 if ( ! source.data->data_sinks.insert(&*this->data).second ||
443 ! data->data_sources.insert(&*source.data).second )
444 EXCEPTION_MNGR(propertyDict_error, "PropertyDict::reference(): "
445 "duplicate reference to an external PropertyDict");
446
447 propertyDict_t::iterator src = source.data->properties.begin();
448 propertyDict_t::iterator srcEnd = source.data->properties.end();
449
450 propertyDict_t::iterator it = data->properties.begin();
451 propertyDict_t::iterator itEnd = data->properties.end();
452
453 typedef
454 map<size_t, pair<propertyDict_t::iterator,propertyDict_t::iterator> >
455 referenceBuffer_t;
456
457 referenceBuffer_t references;
458 referenceBuffer_t::iterator ref_it = references.begin();
459 list<string> promoted;
460
461 try {
462 int test = 0;
463 while ( src != srcEnd )
464 {
465 if ( it == itEnd )
466 test = 1;
467 else
468 test = it->first.compare(src->first);
469
470 if ( test < 0 )
471 ++it;
472 else if ( test == 0 )
473 {
474 if ( exclude.count(it->second->category) == 0 )
475 {
476 if ( ! it->second->privileged() )
477 {
478 ERR_HANDLER( "PropertyDict::reference(): "
479 "non-privileged property, '" << it->first
480 << "' cannot reference remote value." );
481 }
482 else if ( it->second->source.connected() )
483 {
484 ERR_HANDLER( "PropertyDict::reference(): "
485 "attempt to reference property '"
486 << it->first << "', which is already connected "
487 "to a remote value." );
488 }
489 else if ( src->second->promote &&
490 ! block_promotion.count(src->first) )
491 {
492 ERR_HANDLER( "PropertyDict::reference(): "
493 "local property '" << it->first
494 << "', masks a remote promotable property." );
495 }
496 else
497 {
498 references.insert
499 ( references.end(),
500 make_pair(it->second->id, make_pair(it, src)) );
501 }
502 }
503 ++it;
504 ++src;
505 }
506 else
507 {
508 if ( src->second->promote && ! block_promotion.count(src->first) )
509 {
510 data->declare_impl( src->first, new PropertyStore_property
511 ( src->second->property,
512 src->second->category,
513 src->second->promote,
514 src->second->description ) );
515 promoted.push_back(src->first);
516 }
517 ++src;
518 }
519 }
520
521 for(ref_it = references.begin(); ref_it != references.end(); ++ref_it)
522 {
523 Privileged_Property* prop
524 = ref_it->second.first->second->privileged();
525 Property& src = ref_it->second.second->second->property;
526
527 Property::bound_set_t _set = prop->bind_set();
528 ref_it->second.first->second->source =
529 src.onChange().connect(connection_group, _set);
530 prop->set_readonly();
531
532 // copy the current value!
533 try {
534 _set(src);
535 } catch ( std::exception &e ) {
536 EXCEPTION_MNGR( propertyDict_error, "PropertyDict::reference(): "
537 "exception caught while replicating value of '"
538 << ref_it->second.first->first << "':"
539 << std::endl << e.what() );
540 }
541 }
542 } catch ( ... ) {
543 while ( ! promoted.empty() )
544 {
545 erase(promoted.front());
546 promoted.pop_front();
547 }
548 while ( ref_it != references.begin() )
549 {
550 --ref_it;
551 ref_it->second.first->second->source.disconnect();
552 }
553 throw;
554 }
555 #endif // UTILIB_HAVE_BOOST
556 }
557
558
559 void
dereference(PropertyDict & source)560 PropertyDict::dereference( PropertyDict &source )
561 {
562 data->dereference_impl( *source.data );
563 }
564
565 void
dereference_all()566 PropertyDict::dereference_all()
567 {
568 data->dereference_all();
569 }
570
571
write(const PropertyDict::Writer & writer) const572 void PropertyDict::write(const PropertyDict::Writer& writer) const
573 {
574 PropertyDict::Writer& w = const_cast<PropertyDict::Writer&>(writer);
575 propertyDict_t::const_iterator it = data->properties.begin();
576 propertyDict_t::const_iterator itEnd = data->properties.end();
577 w.start(this);
578 for ( ; it != itEnd; ++it )
579 w.item( it->second->property,
580 it->first,
581 it->second->description,
582 it->second->promote != NULL );
583 w.end(this);
584 }
585
586 } // namespace utilib
587