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