Tekkotsu Homepage
Demos
Overview
Downloads
Dev. Resources
Reference
Credits

plistCollections.h

Go to the documentation of this file.
00001 //-*-c++-*-
00002 #ifndef INCLUDED_plistCollections_h_
00003 #define INCLUDED_plistCollections_h_
00004 
00005 #include "plistPrimitives.h"
00006 #include <map>
00007 #include <vector>
00008 #include <set>
00009 #include <regex.h>
00010 namespace plist {
00011   ObjectBase* loadXML(xmlNode* node); // defined in plist.h, we need the prototype
00012 }
00013 
00014 namespace plist {
00015   
00016   //! Provides a common base class for the collection-oriented primitives, Dictionary and Array
00017   /*! 
00018    *  When a collection, you can call addEntry() or setEntry() you can either:
00019    *    - pass a pointer to an ObjectBase or directly pass a primitive value (int, float, char, etc.),
00020    *      in which case the Array will assume management of the corresponding ObjectBase
00021    *      instance (freeing the memory region when removed)
00022    *    - pass a reference to an ObjectBase, in which case you retain control over the object's
00023    *      allocation
00024    *
00025    *  This class supports callbacks upon modification through the use of the
00026    *  CollectionListener interface.  Note that we only store a pointer to the
00027    *  listener list, which is typically unallocated when no listeners are
00028    *  active.  This should ensure minimal memory usage per object, as well as
00029    *  support safe storage of plist objects in inter-process shared memory
00030    *  regions.
00031    *
00032    *  If you are using these in a shared memory region, just be sure that only
00033    *  the process with listeners does any and all modifications, and that it
00034    *  unsubscribes before detaching from the region (or else destroys the region
00035    *  itself)
00036    *
00037    *  There isn't a callback if entries themselves are modified, only when new
00038    *  entries are added, or old ones removed.  If you want to know any time any
00039    *  aspect of any entry is modified, listen for the add and remove callbacks,
00040    *  and then add yourself as a listener to each entry individually.  */
00041   class Collection : public ObjectBase {
00042   public:
00043     //! Specifies that a collection of collections cannot contain any primitive values
00044     template<typename U, typename V> struct conversion_policy { typedef typename U::DeniedValueConversions value_conversion; };
00045     
00046     //! used to define values for LoadSavePolicy values so we can test a bit out of the policy value
00047     enum LoadSaveActionBitMask {
00048       ADDITIONS=1, //!< if this bit is set in #loadPolicy's value, entries not found in the collection will be added, or for #savePolicy, entries will be added to the file
00049       REMOVALS=2 //!< if this bit is set in #loadPolicy's value, entries not found in the file will be removed from the collection, or for #savePolicy, entries will be removed from the file
00050     };
00051     
00052     //! Arguments for setLoadPolicy() and setSavePolicy(), specifies how to handle orphaned entries when loading or saving
00053     /*! An entry is considered "orphaned" if you are loading or saving into a pre-existing file,
00054      *  and an entry exists in one location (the collection or the file), but not the other.
00055      *  The results look like this... (add/remove refer to the destination i.e. collection if loading, file if saving):
00056      *  <table>
00057      *  <tr><td>@b Loading</td><td>SYNC</td><td>INTERSECT</td><td>UNION</td><td>FIXED</td></tr>
00058      *  <tr><td>not in file, in collection</td><td>remove</td><td>remove</td><td>ignore</td><td>ignore</td></tr>
00059      *  <tr><td>in file, not in collection</td><td>add</td><td>ignore</td><td>add</td><td>ignore</td></tr>
00060     *  <tr><td>@b Saving</td><td>SYNC</td><td>INTERSECT</td><td>UNION</td><td>FIXED</td></tr>
00061      *  <tr><td>not in file, in collection</td><td>add</td><td>ignore</td><td>add</td><td>ignore</td></tr>
00062      *  <tr><td>in file, not in collection</td><td>remove</td><td>remove</td><td>ignore</td><td>ignore</td></tr>
00063      *  </table>
00064      *  
00065      *  Commonly, you'll want to use SYNC (the default) to support dynamic storage, and FIXED load/SYNC save
00066      *  for configuration settings (or perhaps FIXED load and UNION save to keep 'extra' values in the file...)*/
00067     enum LoadSavePolicy {
00068       FIXED = 0, //!< destination will have the same entries as before, ignores orphans and only updates entries with matching keys
00069       UNION = ADDITIONS, //!< destination will have its current entries, as well as new ones from the source
00070       INTERSECT = REMOVALS, //!< destination will only hold entries which are in both locations, removes entries not found in source, ignores new entries
00071       SYNC = ADDITIONS|REMOVALS //!< destination will mirror source, new entries are added, missing entries are removed
00072     };
00073 
00074     //! assignment (don't assign listeners); subclass should call fireEntriesChanged after calling this (and updating its own storage)
00075     Collection& operator=(const Collection& d) { if(&d==this) return *this; ObjectBase::operator=(d); return *this; }
00076     
00077     //! destructor
00078     ~Collection();
00079     
00080     //! recursively resolves @a path interpreted as a series of collection entry names separated by '.', returns NULL if it doesn't exist
00081     virtual ObjectBase* resolveEntry(const std::string& path) const=0;
00082     //! remove all entries in one fell swoop
00083     virtual void clear()=0;
00084     //! return the size of the collection
00085     virtual size_t size() const=0;
00086     
00087     //! get notified of changes; be sure to call removeCollectionListener() before destructing @a l!
00088     virtual void addCollectionListener(CollectionListener* l) const;
00089     //! no longer take notification of changes to this object's value
00090     virtual void removeCollectionListener(CollectionListener* l) const;
00091     //! test if @a l is currently registered as a listener
00092     virtual bool isCollectionListener(CollectionListener* l) const;
00093     
00094     void setUnusedWarning(bool b) { warnUnused=b; } //!< set #warnUnused
00095     bool getUnusedWarning() const { return warnUnused; } //!< returns #warnUnused
00096 
00097     virtual LoadSavePolicy getLoadPolicy() const { return loadPolicy; } //!< returns #loadPolicy
00098     virtual LoadSavePolicy getSavePolicy() const { return savePolicy; } //!< returns #savePolicy
00099     virtual void setLoadPolicy(LoadSavePolicy lp) { loadPolicy=lp; } //!< assigns #loadPolicy
00100     virtual void setSavePolicy(LoadSavePolicy sp) { savePolicy=sp; } //!< assigns #savePolicy
00101     virtual void setLoadSavePolicy(LoadSavePolicy lp, LoadSavePolicy sp) { setLoadPolicy(lp); setSavePolicy(sp); } //!< assigns #loadPolicy and #savePolicy respectively
00102     
00103     //! defines separator between sub-collections
00104     /*!  (defined as a function instead of just a constant member so there's no issues with initialization order) */
00105     static const std::string& subCollectionSep() {
00106       static std::string sep(1,'.');
00107       return sep;
00108     }
00109     
00110     //! returns longest key length which matches the regular expression
00111     virtual unsigned int getLongestKeyLen(const regex_t* reg=NULL, unsigned int depth=-1) const=0;
00112     
00113     //! returns true if the Collection subclass allows storage of the argument
00114     virtual bool canContain(const ObjectBase& obj)=0;
00115     
00116     virtual long toLong() const;
00117     virtual double toDouble() const;
00118     
00119   protected:
00120     //! constructor
00121     Collection() : ObjectBase(), collectionListeners(), warnUnused(true), loadPolicy(SYNC), savePolicy(SYNC) {autoFormat=false;}
00122     //! constructor
00123     Collection(LoadSavePolicy lp, LoadSavePolicy sp) : ObjectBase(), collectionListeners(), warnUnused(true), loadPolicy(lp), savePolicy(sp) {autoFormat=false;}
00124     //! copy constructor (don't assign listeners)
00125     Collection(const Collection& d) : ObjectBase(d), collectionListeners(), warnUnused(d.warnUnused), loadPolicy(d.loadPolicy), savePolicy(d.savePolicy) {autoFormat=false;}
00126     
00127     //! run through #collectionListeners, calling CollectionListener::plistCollectionEntryAdded(*this,val)
00128     virtual void fireEntryAdded(ObjectBase& val);
00129     //! run through #collectionListeners, calling CollectionListener::plistCollectionEntryRemoved(*this,val)
00130     virtual void fireEntryRemoved(ObjectBase& val);
00131     //! run through #collectionListeners, calling CollectionListener::plistCollectionEntriesChanged(*this)
00132     virtual void fireEntriesChanged();
00133     //! stores a list of the current listeners
00134     mutable std::set<CollectionListener*>* collectionListeners;
00135     
00136     //! returns index corresponding to @a name, which should encode an integer value less than or equal to the current size
00137     static size_t getIndex(const std::string& name);
00138     
00139     //! returns a prefix for items within the collection
00140     static std::string getIndentationPrefix(xmlNode* node);
00141     
00142     //! when an empty string is needed for not found items
00143     /*!  (defined as a function instead of just a constant member so there's no issues with initialization order) */
00144     static const std::string& emptyStr() {
00145       static std::string mt;
00146       return mt;
00147     }
00148     //! how much to indent each sub-collection
00149     /*!  (defined as a function instead of just a constant member so there's no issues with initialization order) */
00150     static const std::string& perIndent() {
00151       static std::string pi(1,'\t');
00152       return pi;
00153     }
00154     //! when saving comments to file, the key name will automatically be prepended to the comment, unless the key is found within this many characters from the beginning of the comment
00155     static const unsigned int KEY_IN_COMMENT_MAX_POS=10;
00156     
00157     //! if true (the default) loadXML will give warnings using FIXED policy and there are ignored entries in the source while loading or ignored entries in the destination while saving
00158     bool warnUnused;    
00159     //! specifies how to handle "orphaned" entries while loading
00160     LoadSavePolicy loadPolicy;
00161     //! specifies how to handle "orphaned" entries while saving
00162     LoadSavePolicy savePolicy;
00163   };
00164   
00165   
00166   
00167   
00168   //! Maintains a set of (key,value) pairs, see DictionaryOf, and the Dictionary typedef
00169   /*! You can add or set entries by a quite a few variations on addEntry(), setEntry(), or addValue().
00170    *  Basically these boil down to either:
00171    *    - pass a pointer to an ObjectBase or directly pass a primitive value (int, float, char, etc.),
00172    *      in which case the dictionary will assume management of the corresponding ObjectBase
00173    *      instance (freeing the memory region when removed)
00174    *    - pass a reference to an ObjectBase, in which case you retain control over the object's
00175    *      allocation
00176    */
00177   class DictionaryBase : virtual public Collection {
00178     friend std::ostream& operator<<(std::ostream& os, const DictionaryBase& d);
00179   public:
00180     //! shorthand for the type of the storage
00181     typedef std::map<std::string,ObjectBase*> storage_t;
00182     //! shorthand for iterators to be returned
00183     typedef storage_t::iterator iterator;
00184     //! shorthand for iterators to be returned
00185     typedef storage_t::const_iterator const_iterator;
00186     
00187     //! Indicates that no value conversions are allowed
00188     /*! The actual storage type is still allowed, so technically we could use EntryConstraint<PO>
00189      *  instead as the conversion policy, but that doesn't gain anything because you would need
00190      *  to know the PO to test for it.  At least with this you can test for DeniedValueConversions as a base
00191      *  class and then fall back to testing individual PO's if you want. */
00192     struct DeniedValueConversions {
00193       virtual ~DeniedValueConversions() {} //!< no-op destructor
00194     };
00195     
00196     //! This base conversion policy doesn't actually specify any conversions at all -- this serves as a base class to provide the ability to directly add entries of the specified type; the subclasses will add value conversions
00197     /*! This class is useless to end users: to use it they would have to know the template type being used,
00198      *  which if they knew, they could just dynamic_cast to the DictionaryOf type directly.  The point of this
00199      *  class is to provide the abstract functions to the its subclasses, which use them to implement their
00200      *  various conversions. */
00201     template<typename PO>
00202     struct EntryConstraint {
00203       virtual ~EntryConstraint() {} //!< no-op destructor
00204       //! insert a new entry to the dictionary, with key @a name and value @a val (replaces any previous entry by same name, but can give a warning)
00205       virtual void setEntry(const std::string& name, PO& val, bool warnExists=false)=0;
00206       //! insert a new entry to the dictionary, with key @a name and value @a val (replaces any previous entry by same name with a warning)
00207       virtual void addEntry(const std::string& name, PO& val, const std::string& comment="", bool warnExists=true)=0;
00208       //! insert a new entry to the dictionary, with key @a name and value @a val (replaces any previous entry by same name, but can give a warning); control of (de)allocation will be assumed by the dictionary
00209       virtual void setEntry(const std::string& name, PO* val, bool warnExists=false)=0;
00210       //! insert a new entry to the dictionary, with key @a name and value @a val (replaces any previous entry by same name with a warning); control of (de)allocation will be assumed by the dictionary
00211       virtual void addEntry(const std::string& name, PO* val, const std::string& comment="", bool warnExists=true)=0;
00212     };
00213     
00214     //! Abstract base class to test whether the collection will accept strings (possibly converting to a value type, or storing directly as string depending on concrete type)
00215     /*! The point of this class is to handle the situation where you have a DictionaryBase and user input to append, and
00216      *  you don't want to have to test every combination of template parameters to DictionaryOf to find out if you can
00217      *  add the data.  Instead, test dynamic_cast<plist::DictionaryBase::StringConversion>, and if it is successful, you
00218      *  can pass the string without having to know the actual value type of the dictionary. */
00219     struct StringConversion {
00220       virtual ~StringConversion() {} //!< no-op destructor
00221       //! generic addition of value, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00222       virtual void addValue(const std::string& name, const std::string& val, const std::string& comment="", bool warnExists=true)=0;
00223     };
00224     
00225     //! Abstract base class to test whether the collection will accept integers (possibly converting to another value type, or storing directly as a [unsigned] long depending on concrete type)
00226     /*! The point of this class is to handle the situation where you have a DictionaryBase and user input to append, and
00227      *  you don't want to have to test every combination of template parameters to DictionaryOf to find out if you can
00228      *  add the data.  Instead, test dynamic_cast<plist::DictionaryBase::IntegerConversion>, and if it is successful, you
00229      *  can pass the data without having to know the actual value type of the dictionary. */
00230     struct IntegerConversion {
00231       virtual ~IntegerConversion() {} //!< no-op destructor
00232       //! generic addition of value, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00233       virtual void addValue(const std::string& name, long val, const std::string& comment="", bool warnExists=true)=0;
00234       //! generic addition of value, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00235       virtual void addValue(const std::string& name, unsigned long val, const std::string& comment="", bool warnExists=true)=0;
00236     };
00237     
00238     //! Abstract base class to test whether the collection will accept floating point numbers (possibly converting to another value type, or storing directly as a double depending on concrete type)
00239     /*! The point of this class is to handle the situation where you have a DictionaryBase and user input to append, and
00240      *  you don't want to have to test every combination of template parameters to DictionaryOf to find out if you can
00241      *  add the data.  Instead, test dynamic_cast<plist::DictionaryBase::RealConversion>, and if it is successful, you
00242      *  can pass the data without having to know the actual value type of the dictionary. */
00243     struct RealConversion {
00244       virtual ~RealConversion() {} //!< no-op destructor
00245       //! generic addition of value, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00246       virtual void addValue(const std::string& name, double val, const std::string& comment="", bool warnExists=true)=0;
00247     };
00248     
00249     //! This conversion policy accepts entries of the specified template type, and will try to create new instances of that type constructed from any values which are passed.
00250     /*! Use of this conversion policy requires that the template parameter is not abstract,
00251      *  as the policy will be trying to create new instances directly from the specified type. */
00252     template<typename PO>
00253     struct ConversionTo : public StringConversion, public IntegerConversion, public RealConversion, public EntryConstraint<PO> {
00254       //! insert a new entry to the map, and corresponding comment; expects @a val to be either a primitive type, like int, float, etc., or one of the variable-sized Collection's, like Array, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00255       template<typename T>
00256       void addValue(const std::string& name, const T& val, const std::string& comment="", bool warnExists=true) { addEntry(name,new PO(val),comment,warnExists); }
00257       virtual void addValue(const std::string& name, const std::string& val, const std::string& comment="", bool warnExists=true) { PO * po=new PO; try { po->set(val); } catch(...) { delete po; throw; } addEntry(name,po,comment,warnExists); }
00258       //! "specialization" (actually just another override) for handling character arrays as strings, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00259       virtual void addValue(const std::string& name, char val[], const std::string& comment="", bool warnExists=true) { PO * po=new PO; try { po->set(val); } catch(...) { delete po; throw; } addEntry(name,po,comment,warnExists); }
00260       //! "specialization" (actually just another override) for handling character arrays as strings, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00261       virtual void addValue(const std::string& name, const char val[], const std::string& comment="", bool warnExists=true) { PO * po=new PO; try { po->set(val); } catch(...) { delete po; throw; } addEntry(name,po,comment,warnExists); }
00262       virtual void addValue(const std::string& name, long val, const std::string& comment="", bool warnExists=true) { addEntry(name,new PO(val),comment,warnExists); }
00263       virtual void addValue(const std::string& name, unsigned long val, const std::string& comment="", bool warnExists=true) { addEntry(name,new PO(val),comment,warnExists); }
00264       virtual void addValue(const std::string& name, double val, const std::string& comment="", bool warnExists=true) { addEntry(name,new PO(val),comment,warnExists); }
00265     };
00266     
00267     //! This conversion policy accepts any entries of the specified template type, values will be directly wrapped as Primitives so no conversion at all is actually performed
00268     /*! Use addEntry() to add ObjectBase subclasses -- addValue is for POD types */
00269     template<typename PO>
00270     struct WrapValueConversion : public StringConversion, public IntegerConversion, public RealConversion, public EntryConstraint<PO> {
00271       //! insert a new entry to the map, and corresponding comment; expects @a val to be either a primitive type, like int, float, etc., control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00272       template<typename T>
00273       void addValue(const std::string& name, const T& val, const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<T>(val),comment,warnExists); }
00274       virtual void addValue(const std::string& name, const std::string& val, const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<std::string>(val),comment,warnExists); }
00275       //! "specialization" (actually just another override) for handling character arrays as strings, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00276       virtual void addValue(const std::string& name, char val[], const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<std::string>(val),comment,warnExists); }
00277       //! "specialization" (actually just another override) for handling character arrays as strings, control of (de)allocation of corresponding Primitive instance will be assumed by the dictionary
00278       virtual void addValue(const std::string& name, const char val[], const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<std::string>(val),comment,warnExists); }
00279       virtual void addValue(const std::string& name, long val, const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<long>(val),comment,warnExists); }
00280       virtual void addValue(const std::string& name, unsigned long val, const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<unsigned long>(val),comment,warnExists); }
00281       virtual void addValue(const std::string& name, double val, const std::string& comment="", bool warnExists=true) { this->addEntry(name,new Primitive<double>(val),comment,warnExists); }
00282     };
00283     
00284     //! remove the entry with key @a name, returns true if something was actually removed (if false, wasn't there to begin with)
00285     virtual bool removeEntry(const std::string& name);
00286     
00287     //! change the key for an entry from @a oldname to @a newname, returns false if @a oldname didn't exist (thus no change was made)
00288     /*! If the collection owns the reference to the object, you have to use this function instead
00289      *  of a pair of calls to removeEntry/addEntry, otherwise you will wind up with an invalid pointer! */
00290     virtual bool renameEntry(const std::string& oldname, const std::string& newname);
00291     //! exchange the values for a pair of keys -- if either key doesn't exist, forwards call to renameEntry()
00292     /*! returns true if the swap was successful, only returns false if both keys are invalid */
00293     virtual bool swapEntry(const std::string& a, const std::string& b);
00294     
00295     //! returns a reference to the entry with the specified name, creating it if it doesn't exist
00296     virtual ObjectBase& getEntry(const std::string& name)=0;
00297     //! returns a reference to the entry with the specified name, creating it if it doesn't exist
00298     virtual ObjectBase& operator[](const std::string& name)=0;
00299     //! returns a pointer to entry with the specified 'path', which may recurse through sub-collections
00300     virtual ObjectBase* resolveEntry(const std::string& path) const;
00301         
00302     //! returns an iterator to an entry in the current dictionary
00303     const_iterator findEntry(const std::string& name) const { return dict.find(name); }
00304     
00305     virtual void clear();
00306     
00307     //! return an STL const_iterator to the first entry
00308     const_iterator begin() const { return dict.begin(); }
00309     //! return the one-past-end const_iterator
00310     const_iterator end() const { return dict.end(); }
00311     //! return the size of the dictionary
00312     virtual size_t size() const { return dict.size(); }
00313     
00314     //! replaces previous comment for @a name, or adds it if it doesn't already exist (can preceed actual entry!)
00315     void setComment(const std::string& name, const std::string& comment);
00316     //! returns comment retrieved from loaded file, or any subsequent call to setComment
00317     const std::string& getComment(const std::string& name) const;
00318     
00319     virtual void loadXML(xmlNode* node);
00320     virtual void saveXML(xmlNode* node) const { std::set<std::string> seen; saveXML(node,!(savePolicy&ADDITIONS),seen); }
00321     //! saves the dictionary into the specified node
00322     /*! @param[in] node the xml node which should be saved into
00323      *  @param[in] onlyOverwrite if is true, only saves entries for keys already found in the node (this overrides the current savePolicy value)
00324      *  @param[in] seen used to keep track of which nodes have been seen in @a node -- may be of particular interest with @a onlyOverride set
00325      *
00326      *  @a seen is not cleared before being used.*/
00327     virtual void saveXML(xmlNode* node, bool onlyOverwrite, std::set<std::string>& seen) const;
00328     
00329     virtual std::string toString() const;
00330     
00331     //! returns the length of the longest key, subject to an optional regular expression and max depth
00332     virtual unsigned int getLongestKeyLen(const regex_t* reg=NULL, unsigned int depth=-1) const;
00333     
00334     //! returns true if the specified object will be deallocated when removed from the dictionary
00335     bool ownsReference(ObjectBase * val) const { return myRef.find(val)==myRef.end(); }
00336     
00337   protected:
00338     //! constructor
00339     explicit DictionaryBase(bool growable) : Collection(growable?UNION:FIXED,SYNC), dict(), myRef(), comments() { setLoadSavePolicy(growable?UNION:FIXED,SYNC); }
00340     //! copy constructor (don't assign listeners)
00341     DictionaryBase(const DictionaryBase& d) : Collection(d), dict(d.dict), myRef(d.myRef), comments(d.comments) { cloneMyRef(); setLoadSavePolicy(d.getLoadPolicy(),d.getSavePolicy()); }
00342     //! assignment (don't assign listeners)
00343     DictionaryBase& operator=(const DictionaryBase& d) { Collection::operator=(d); return *this; }
00344     
00345     //! destructor
00346     ~DictionaryBase() { clear(); }
00347     
00348     //! indicates that the storage implementation should mark this as an externally supplied heap reference, which needs to be deleted on removal/destruction
00349     virtual void takeObject(const std::string& name, ObjectBase* obj);
00350     
00351     virtual void fireEntryRemoved(ObjectBase& val);
00352     
00353     //! returns an entry matching just the prefix
00354     /*! @param[in] name the name being looked up
00355      *  @param[out] seppos the position of the separator (sub-collections are separated by '.')
00356      *  @return iterator of the sub entry*/
00357     iterator getSubEntry(const std::string& name, std::string::size_type& seppos);
00358     //! returns an entry matching just the prefix
00359     /*! @param[in] name the name being looked up
00360      *  @param[out] seppos the position of the separator (sub-collections are separated by '.')
00361      *  @return iterator of the sub entry*/
00362     const_iterator getSubEntry(const std::string& name, std::string::size_type& seppos) const;
00363     
00364     //! called after an assignment or copy to clone the objects in #myRef to perform a deep copy
00365     virtual void cloneMyRef();
00366     
00367     //! called with each node being loaded so subclass can handle appropriately
00368     virtual bool loadXMLNode(const std::string& key, xmlNode* val, const std::string& comment)=0;
00369     
00370     //! called with each node being saved so subclass can handle appropriately, return true if successful and reset key if changed
00371     virtual bool saveOverXMLNode(xmlNode* k, xmlNode* val, const std::string& key, std::string comment, const std::string& indentStr, std::set<std::string>& seen) const;
00372     
00373     //! called with each node being saved so subclass can handle appropriately, return true if successful and set key
00374     virtual void saveXMLNode(xmlNode* node, const std::string& key, const ObjectBase* val, const std::string& indentStr) const;
00375     
00376     //! storage of entries -- mapping from keys to value pointers
00377     storage_t dict;
00378     
00379     //! objects which have been handed over to the collection for eventual de-allocation
00380     std::set<ObjectBase*> myRef;
00381 
00382     //! shorthand for the type of #comments
00383     typedef std::map<std::string,std::string> comments_t;
00384     //! storage of entry comments -- mapping from keys to help text comments for manual editing or user feedback
00385     /*! not every key necessarily has a comment! */
00386     comments_t comments;
00387   };
00388   
00389   //! A dictionary which requires all elements to be subtypes of the PO template argument
00390   /*! You can add or set entries by a quite a few variations on addEntry(), setEntry(), and addValue (via the Alloc conversion policy)
00391    *  Basically these boil down to either:
00392    *    - pass a pointer to an ObjectBase or directly pass a primitive value (int, float, char, etc.),
00393    *      in which case the dictionary will assume management of the corresponding ObjectBase
00394    *      instance (freeing the memory region when removed)
00395    *    - pass a reference to an ObjectBase, in which case you retain control over the object's
00396    *      allocation
00397    *  
00398    *  You have probably noticed this is a templated class -- you can provide any of the ObjectBase
00399    *  subclasses to restrict the storage to that particular type, which will make life easier when
00400    *  retrieving objects since their type will be known.
00401    *
00402    *  However, if you @e want an dictionary of mixed types, you can pass ObjectBase itself for the
00403    *  template parameter, and you can then insert any combination of the plist types into the
00404    *  same dictionary.  For convenience, a plist::Dictionary typedef is provided which does exactly this.
00405    *  
00406    *  So plist::Dictionary can handle any mixture of types, whereas plist::DictionaryOf<PO> will @e only
00407    *  accept the plist objects of type PO (or their subclasses).  The Alloc template argument
00408    *  allows you to define how new string values will be handled from DictionaryBase.
00409    *
00410    *  The optional conversion policy template specifies a base class for the dictionary which
00411    *  can control how the dictionary will handle conversions from non-PO-based types.
00412    */
00413   template<typename PO, typename Alloc=typename PO::template conversion_policy<DictionaryBase,PO>::value_conversion >
00414   class DictionaryOf : public DictionaryBase, public Alloc {
00415     /// @cond INTERNAL
00416     typedef typename storage_t::const_iterator::iterator_category const_iterator_category;
00417     typedef typename std::pair<storage_t::key_type,PO*> const_iterator_value;
00418     typedef typename storage_t::const_iterator::difference_type const_iterator_difference;
00419     /// @endcond
00420   public:
00421     //! shorthand for the type of the storage
00422     typedef typename DictionaryBase::storage_t storage_t;
00423     
00424     /// @cond INTERNAL
00425     
00426     //! iterator implementation which wraps storage_t::const_iterator to transparently dynamic_cast to the PO for client
00427     class const_iterator : std::iterator<const_iterator_category, const_iterator_value, const_iterator_difference> {
00428     public:
00429       const_iterator(const storage_t::const_iterator& sit) : it(sit), tmp() {}
00430       const const_iterator_value& operator*() const { return std::make_pair(it->first,dynamic_cast<PO*>(it->second)); }
00431       const const_iterator_value* operator->() const { tmp=std::make_pair(it->first,dynamic_cast<PO*>(it->second)); return &tmp; }
00432       const_iterator& operator++() { ++it; return *this; }
00433       const_iterator operator++(int) { return const_iterator(it++); }
00434