]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Improve the documentation of Collection
[libs/datafile.git] / source / collection.h
1 #ifndef MSP_DATAFILE_COLLECTION_H_
2 #define MSP_DATAFILE_COLLECTION_H_
3
4 #include <msp/core/maputils.h>
5 #include <msp/core/meta.h>
6 #include <msp/core/refptr.h>
7 #include "loader.h"
8
9 namespace Msp {
10 namespace DataFile {
11
12 /**
13 Helper struct to determine whether a Loader has a Collection typedef.
14 */
15 template<typename T>
16 struct NeedsCollection
17 {
18         struct Yes { char c[2]; };
19         struct No { char c; };
20
21         template<typename U>
22         static Yes f(typename U::Collection *);
23         template<typename U>
24         static No f(...);
25
26         enum { value = (sizeof(f<T>(0))==sizeof(Yes)) };
27 };
28
29 class CollectionItemTypeBase;
30
31 template<typename T>
32 class CollectionItemType;
33
34 /**
35 A collection of objects that can be loaded from a datafile.  Each object is
36 identified by a name, which must be unique across the entire collection.
37
38 While this class can be instantiated by itself and used for storing objects,
39 loading requires that a subclass defines the supported types.  See the add_type
40 method for details.
41 */
42 class Collection
43 {
44 public:
45         /**
46         Loads objects into a Collection.  Automatically picks up keywords from
47         defined item types.
48         */
49         class Loader: public DataFile::Loader
50         {
51                 template<typename T> friend class CollectionItemType;
52
53         private:
54                 template<typename T, typename S, bool = NeedsCollection<typename T::Loader>::value>
55                 struct Add;
56
57                 Collection &coll;
58
59         public:
60                 Loader(Collection &);
61                 Collection &get_object() const { return coll; }
62         private:
63                 template<typename T, typename S, typename C>
64                 void coll_item(const std::string &n)
65                 {
66                         RefPtr<T> it = new T;
67                         load_sub(*it, dynamic_cast<C &>(coll));
68                         coll.add<S>(n, it.get());
69                         it.release();
70                 }
71
72                 template<typename T, typename S>
73                 void item(const std::string &n)
74                 {
75                         RefPtr<T> it = new T;
76                         load_sub(*it);
77                         coll.add<S>(n, it.get());
78                         it.release();
79                 }
80         };
81
82 private:
83         typedef std::map<std::string, Variant> ItemMap;
84         typedef std::list<CollectionItemTypeBase *> TypeList;
85
86         TypeList types;
87         ItemMap items;
88
89         Collection(const Collection &);
90         Collection &operator=(const Collection &);
91 public:
92         Collection() { }
93         virtual ~Collection();
94
95         /** Adds an object into the collection.  The name must not pre-exist.  The
96         collection takes ownership of the object. */
97         template<typename T>
98         void add(const std::string &name, T *item)
99         {
100                 if(!item)
101                         throw std::invalid_argument("Collection::add(item)");
102
103                 RefPtr<typename RemoveConst<T>::Type> ptr(item);
104                 try
105                 {
106                         insert_unique(items, name, ptr);
107                 }
108                 catch(...)
109                 {
110                         // Avoid deleting the object
111                         ptr.release();
112                         throw;
113                 }
114         }
115
116         /// Gets a typed object from the collection.
117         template<typename T>
118         T &get(const std::string &name) const
119         {
120                 typedef typename RemoveConst<T>::Type NCT;
121
122                 return *get_item(items, name).value<RefPtr<NCT> >();
123         }
124
125         /** Gets a typed object from the collection.  If the name is not found in
126         and a creator for the item type is defined, it is invoked. */
127         template<typename T>
128         T &get(const std::string &);
129
130         /// Returns a list of the names of objects of one type in the collection.
131         template<typename T>
132         std::list<std::string> get_names() const
133         {
134                 std::list<std::string> result;
135                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
136                         if(i->second.check_type<RefPtr<typename RemoveConst<T>::Type> >())
137                                 result.push_back(i->first);
138                 return result;
139         }
140
141         /// Returns a list of objects of one type in the collection.
142         template<typename T>
143         std::list<T *> get_list() const
144         {
145                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
146
147                 std::list<T *> result;
148                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
149                         if(i->second.check_type<RPNCT>())
150                                 result.push_back(i->second.value<RPNCT>().get());
151                 return result;
152         }
153
154         /** Checks whether a name exists in the collection.  Does not care about the
155         type of the object. */
156         bool contains(const std::string &n) const;
157
158         /// Returns the name of an item in the collection.
159         template<typename T>
160         const std::string &get_name(T *d) const
161         {
162                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
163
164                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
165                         if(i->second.check_type<RPNCT>())
166                                 if(i->second.value<RPNCT>().get()==d)
167                                         return i->first;
168         
169                 // XXX Need better exception class
170                 throw std::runtime_error("Item not found in collection");
171         }
172
173 protected:
174         /** Adds a type to the collection.  The returned descriptor object reference
175         can be used to define how objects of that type can be loaded. */
176         template<typename T>
177         CollectionItemType<T> &add_type();
178 };
179
180
181 template<typename T, typename S>
182 struct Collection::Loader::Add<T, S, false>
183 {
184         static void add(Loader &loader, const std::string &kwd)
185         { loader.add(kwd, &Loader::item<T, S>); }
186 };
187
188 template<typename T, typename S>
189 struct Collection::Loader::Add<T, S, true>
190 {
191         static void add(Loader &loader, const std::string &kwd)
192         { loader.add(kwd, &Loader::coll_item<T, S, typename T::Loader::Collection>); }
193 };
194
195
196 class CollectionItemTypeBase
197 {
198 protected:
199         class TagBase
200         {
201         protected:
202                 TagBase() { }
203         public:
204                 virtual ~TagBase() { }
205         };
206
207         template<typename T>
208         class Tag: public TagBase
209         {
210         public:
211                 virtual ~Tag() { }
212         };
213
214         std::string kwd;
215         TagBase *tag;
216
217         CollectionItemTypeBase(): tag(0) { }
218 public:
219         virtual ~CollectionItemTypeBase()
220         { delete tag; }
221
222         virtual void add_to_loader(Collection::Loader &) const = 0;
223         virtual bool can_create() const = 0;
224         virtual void create_item(Collection &, const std::string &) const = 0;
225
226         template<typename T>
227         bool check_type() const
228         { return dynamic_cast<Tag<T> *>(tag); }
229 };
230
231
232 /**
233 Describes a type of item that can be loaded by a Collection.  These are created
234 by Collection::add_type.
235 */
236 template<typename T>
237 class CollectionItemType: public CollectionItemTypeBase
238 {
239 private:
240         class CreatorBase
241         {
242         protected:
243                 CreatorBase() { }
244         public:
245                 virtual ~CreatorBase() { }
246
247                 virtual T *create(Collection &, const std::string &) const = 0;
248         };
249
250         template<typename C>
251         class Creator: public CreatorBase
252         {
253         public:
254                 typedef T *(C::*FuncPtr)(const std::string &);
255
256         private:
257                 FuncPtr func;
258
259         public:
260                 Creator(FuncPtr f): func(f) { }
261
262                 virtual T *create(Collection &coll, const std::string &name) const
263                 { return (static_cast<C &>(coll).*func)(name); }
264         };
265
266         class StoreBase
267         {
268         protected:
269                 StoreBase() { }
270         public:
271                 virtual ~StoreBase() { }
272
273                 virtual void store(Collection &, const std::string &, T *) = 0;
274
275                 virtual void add_to_loader(Collection::Loader &, const std::string &) = 0;
276         };
277
278         template<typename S>
279         class Store: public StoreBase
280         {
281         public:
282                 virtual void store(Collection &coll, const std::string &name, T *obj)
283                 { coll.add(name, static_cast<S *>(obj)); }
284
285                 virtual void add_to_loader(Collection::Loader &loader, const std::string &kwd)
286                 { Collection::Loader::Add<T, S>::add(loader, kwd); }
287         };
288
289         CreatorBase *creat;
290         StoreBase *store;
291
292 public:
293         CollectionItemType():
294                 creat(0), store(new Store<T>)
295         { tag = new Tag<T>; }
296
297         ~CollectionItemType()
298         {
299                 delete creat;
300                 delete store;
301         }
302
303         /** Sets a datafile keyword for this item type.  The Collection's loader
304         will accept a statement with this keyword and a single string argument - the
305         item's name. */
306         CollectionItemType &keyword(const std::string &k)
307         {
308                 kwd = k;
309                 return *this;
310         }
311
312         /** Attaches a creator function to this item type.  If an item is not found
313         in the Collection, the creator function for its type is called to create it.
314         The function must be a member of the Collection subclass containing the
315         type.  It must return the created object, or null if it could not be
316         created.  It's also permissible to load the item via other means and then
317         return null. */
318         template<typename C>
319         CollectionItemType &creator(T *(C::*func)(const std::string &))
320         {
321                 delete creat;
322                 creat = new Creator<C>(func);
323                 return *this;
324         }
325
326         /** Specifies the storage type for items of this type.  It must be a base
327         class of the actual type.  */
328         template<typename S>
329         CollectionItemType &store_as()
330         {
331                 delete tag;
332                 tag = new Tag<S>;
333                 delete store;
334                 store = new Store<S>;
335                 return *this;
336         }
337
338         virtual void add_to_loader(Collection::Loader &loader) const
339         { store->add_to_loader(loader, kwd); }
340
341         virtual bool can_create() const
342         { return creat!=0; }
343
344         virtual void create_item(Collection &coll, const std::string &name) const
345         {
346                 if(!creat)
347                         throw std::runtime_error("no creator");
348                 T *obj = creat->create(coll, name);
349                 if(obj)
350                         store->store(coll, name, obj);
351         }
352 };
353
354
355 template<typename T>
356 T &Collection::get(const std::string &name)
357 {
358         typedef typename RemoveConst<T>::Type NCT;
359
360         if(!items.count(name))
361         {
362                 for(TypeList::iterator i=types.begin(); i!=types.end(); ++i)
363                         if((*i)->can_create() && (*i)->check_type<NCT>())
364                                 (*i)->create_item(*this, name);
365         }
366
367         return *get_item(items, name).value<RefPtr<NCT> >();
368 }
369
370 template<typename T>
371 CollectionItemType<T> &Collection::add_type()
372 {
373         CollectionItemType<T> *type = new CollectionItemType<T>;
374         types.push_back(type);
375         return *type;
376 }
377
378 } // namespace DataFile
379 } // namespace Msp
380
381 #endif