]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Introduce the concept of future objects
[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 Collections also support a notion of "future objects".  These are objects which
43 are known to be possible to load, but loading them is deferred to the first
44 time they are requested.
45 */
46 class Collection
47 {
48 public:
49         /**
50         Loads objects into a Collection.  Automatically picks up keywords from
51         defined item types.
52         */
53         class Loader: public DataFile::Loader
54         {
55                 template<typename T> friend class CollectionItemType;
56
57         private:
58                 template<typename T, typename S, bool = NeedsCollection<typename T::Loader>::value>
59                 struct Add;
60
61                 Collection &coll;
62
63         public:
64                 Loader(Collection &);
65                 Collection &get_object() const { return coll; }
66         private:
67                 template<typename T, typename S, typename C>
68                 void coll_item(const std::string &n)
69                 {
70                         RefPtr<T> it = new T;
71                         load_sub(*it, dynamic_cast<C &>(coll));
72                         coll.add<S>(n, it.get());
73                         it.release();
74                 }
75
76                 template<typename T, typename S>
77                 void item(const std::string &n)
78                 {
79                         RefPtr<T> it = new T;
80                         load_sub(*it);
81                         coll.add<S>(n, it.get());
82                         it.release();
83                 }
84         };
85
86 private:
87         typedef std::map<std::string, Variant> ItemMap;
88         typedef std::list<CollectionItemTypeBase *> TypeList;
89
90         TypeList types;
91         ItemMap items;
92
93         Collection(const Collection &);
94         Collection &operator=(const Collection &);
95 public:
96         Collection() { }
97         virtual ~Collection();
98
99         /** Adds an object into the collection.  The name must not pre-exist.  The
100         collection takes ownership of the object. */
101         template<typename T>
102         void add(const std::string &name, T *item)
103         {
104                 if(!item)
105                         throw std::invalid_argument("Collection::add(item)");
106
107                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
108
109                 ItemMap::iterator i = items.find(name);
110                 if(i!=items.end())
111                 {
112                         if(i->second.check_type<RPNCT>())
113                         {
114                                 // Replace a future object placeholder
115                                 RPNCT &ptr = i->second.value<RPNCT>();
116                                 if(!ptr)
117                                 {
118                                         ptr = item;
119                                         return;
120                                 }
121                         }
122
123                         throw key_error(typeid(ItemMap));
124                 }
125
126                 items.insert(ItemMap::value_type(name, RPNCT(item)));
127         }
128
129 protected:
130         /** Adds the name of a future object to the collection.  The object itself
131         will be loaded on first access.  The calling subclass should be prepared to
132         create the object on request. */
133         template<typename T>
134         void add_future(const std::string &name)
135         {
136                 RefPtr<typename RemoveConst<T>::Type> ptr(0);
137                 insert_unique(items, name, ptr);
138         }
139
140 public:
141         /// Gets a typed object from the collection.
142         template<typename T>
143         T &get(const std::string &name) const
144         {
145                 typedef typename RemoveConst<T>::Type NCT;
146
147                 T *ptr = get_item(items, name).value<RefPtr<NCT> >();
148                 if(!ptr)
149                         throw key_error(typeid(ItemMap));
150                 return *ptr;
151         }
152
153         /** Gets a typed object from the collection.  If the name is not found in
154         and a creator for the item type is defined, it is invoked. */
155         template<typename T>
156         T &get(const std::string &);
157
158 private:
159         template<typename T>
160         void collect_items(std::list<T *> *objects, std::list<std::string> *names, std::list<std::string> *future_names)
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                         {
167                                 T *ptr = i->second.value<RPNCT>().get();
168                                 if(ptr)
169                                 {
170                                         if(objects)
171                                                 objects->push_back(ptr);
172                                         if(names)
173                                                 names->push_back(i->first);
174                                 }
175                                 else if(future_names)
176                                         future_names->push_back(i->first);
177                         }
178         }
179
180 public:
181         /** Returns a list of the names of loaded objects of one type in the
182         collection. */
183         template<typename T>
184         std::list<std::string> get_names() const
185         {
186                 std::list<std::string> result;
187                 collect_items<T>(0, &result, 0);
188                 return result;
189         }
190
191         /** Returns a list of the names of objects of one type in the collection,
192         including any future objects. */
193         template<typename T>
194         std::list<std::string> get_names()
195         {
196                 std::list<std::string> result;
197                 collect_items<T>(0, &result, &result);
198                 return result;
199         }
200
201         /// Returns a list of loaded objects of one type in the collection.
202         template<typename T>
203         std::list<T *> get_list() const
204         {
205                 std::list<T *> result;
206                 collect_items<T>(&result, 0, 0);
207                 return result;
208         }
209
210         /** Returns a list of objects of one type in the collection.  Any future
211         objects of that type are loaded and returned in the list. */
212         template<typename T>
213         std::list<T *> get_list()
214         {
215                 std::list<T *> result;
216                 std::list<std::string> future;
217                 collect_items<T>(&result, 0, &future);
218                 for(std::list<std::string>::iterator i=future.begin(); i!=future.end(); ++i)
219                         result.push_back(&get<T>(*i));
220                 return result;
221         }
222
223 private:
224         template<typename T>
225         unsigned get_status(const std::string &name) const
226         {
227                 ItemMap::const_iterator i = items.find(name);
228                 if(i==items.end())
229                         return false;
230
231                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
232                 if(!i->second.check_type<RPNCT>())
233                         return false;
234
235                 T *ptr = i->second.value<RPNCT>().get();
236                 return ptr ? 1 : 2;
237         }
238
239 public:
240         /// Checks whether a typed object exists and is loaded in the collection.
241         template<typename T>
242         bool contains(const std::string &name) const
243         { return get_status<T>(name)==1; }
244
245         /** Checks whether a typed object exists in the collection, as either a
246         loaded or future object. */
247         template<typename T>
248         bool contains(const std::string &name)
249         { return get_status<T>(name)>0; }
250
251         /// Returns the name of an item in the collection.
252         template<typename T>
253         const std::string &get_name(T *d) const
254         {
255                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
256
257                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
258                         if(i->second.check_type<RPNCT>())
259                                 if(i->second.value<RPNCT>().get()==d)
260                                         return i->first;
261         
262                 // XXX Need better exception class
263                 throw std::runtime_error("Item not found in collection");
264         }
265
266 protected:
267         /** Adds a type to the collection.  The returned descriptor object reference
268         can be used to define how objects of that type can be loaded. */
269         template<typename T>
270         CollectionItemType<T> &add_type();
271 };
272
273
274 template<typename T, typename S>
275 struct Collection::Loader::Add<T, S, false>
276 {
277         static void add(Loader &loader, const std::string &kwd)
278         { loader.add(kwd, &Loader::item<T, S>); }
279 };
280
281 template<typename T, typename S>
282 struct Collection::Loader::Add<T, S, true>
283 {
284         static void add(Loader &loader, const std::string &kwd)
285         { loader.add(kwd, &Loader::coll_item<T, S, typename T::Loader::Collection>); }
286 };
287
288
289 class CollectionItemTypeBase
290 {
291 protected:
292         class TagBase
293         {
294         protected:
295                 TagBase() { }
296         public:
297                 virtual ~TagBase() { }
298         };
299
300         template<typename T>
301         class Tag: public TagBase
302         {
303         public:
304                 virtual ~Tag() { }
305         };
306
307         std::string kwd;
308         TagBase *tag;
309
310         CollectionItemTypeBase(): tag(0) { }
311 public:
312         virtual ~CollectionItemTypeBase()
313         { delete tag; }
314
315         virtual void add_to_loader(Collection::Loader &) const = 0;
316         virtual bool can_create() const = 0;
317         virtual void create_item(Collection &, const std::string &) const = 0;
318
319         template<typename T>
320         bool check_type() const
321         { return dynamic_cast<Tag<T> *>(tag); }
322 };
323
324
325 /**
326 Describes a type of item that can be loaded by a Collection.  These are created
327 by Collection::add_type.
328 */
329 template<typename T>
330 class CollectionItemType: public CollectionItemTypeBase
331 {
332 private:
333         class CreatorBase
334         {
335         protected:
336                 CreatorBase() { }
337         public:
338                 virtual ~CreatorBase() { }
339
340                 virtual T *create(Collection &, const std::string &) const = 0;
341         };
342
343         template<typename C>
344         class Creator: public CreatorBase
345         {
346         public:
347                 typedef T *(C::*FuncPtr)(const std::string &);
348
349         private:
350                 FuncPtr func;
351
352         public:
353                 Creator(FuncPtr f): func(f) { }
354
355                 virtual T *create(Collection &coll, const std::string &name) const
356                 { return (static_cast<C &>(coll).*func)(name); }
357         };
358
359         class StoreBase
360         {
361         protected:
362                 StoreBase() { }
363         public:
364                 virtual ~StoreBase() { }
365
366                 virtual void store(Collection &, const std::string &, T *) = 0;
367
368                 virtual void add_to_loader(Collection::Loader &, const std::string &) = 0;
369         };
370
371         template<typename S>
372         class Store: public StoreBase
373         {
374         public:
375                 virtual void store(Collection &coll, const std::string &name, T *obj)
376                 { coll.add(name, static_cast<S *>(obj)); }
377
378                 virtual void add_to_loader(Collection::Loader &loader, const std::string &kwd)
379                 { Collection::Loader::Add<T, S>::add(loader, kwd); }
380         };
381
382         CreatorBase *creat;
383         StoreBase *store;
384
385 public:
386         CollectionItemType():
387                 creat(0), store(new Store<T>)
388         { tag = new Tag<T>; }
389
390         ~CollectionItemType()
391         {
392                 delete creat;
393                 delete store;
394         }
395
396         /** Sets a datafile keyword for this item type.  The Collection's loader
397         will accept a statement with this keyword and a single string argument - the
398         item's name. */
399         CollectionItemType &keyword(const std::string &k)
400         {
401                 kwd = k;
402                 return *this;
403         }
404
405         /** Attaches a creator function to this item type.  If an item is not found
406         in the Collection, the creator function for its type is called to create it.
407         The function must be a member of the Collection subclass containing the
408         type.  It must return the created object, or null if it could not be
409         created.  It's also permissible to load the item via other means and then
410         return null. */
411         template<typename C>
412         CollectionItemType &creator(T *(C::*func)(const std::string &))
413         {
414                 delete creat;
415                 creat = new Creator<C>(func);
416                 return *this;
417         }
418
419         /** Specifies the storage type for items of this type.  It must be a base
420         class of the actual type.  */
421         template<typename S>
422         CollectionItemType &store_as()
423         {
424                 delete tag;
425                 tag = new Tag<S>;
426                 delete store;
427                 store = new Store<S>;
428                 return *this;
429         }
430
431         virtual void add_to_loader(Collection::Loader &loader) const
432         { store->add_to_loader(loader, kwd); }
433
434         virtual bool can_create() const
435         { return creat!=0; }
436
437         virtual void create_item(Collection &coll, const std::string &name) const
438         {
439                 if(!creat)
440                         throw std::runtime_error("no creator");
441                 T *obj = creat->create(coll, name);
442                 if(obj)
443                         store->store(coll, name, obj);
444         }
445 };
446
447
448 template<typename T>
449 T &Collection::get(const std::string &name)
450 {
451         typedef typename RemoveConst<T>::Type NCT;
452
453         ItemMap::iterator i = items.find(name);
454         if(i!=items.end())
455         {
456                 NCT *ptr = i->second.value<RefPtr<NCT> >().get();
457                 if(ptr)
458                         return *ptr;
459         }
460
461         for(TypeList::iterator j=types.begin(); j!=types.end(); ++j)
462                 if((*j)->can_create() && (*j)->check_type<NCT>())
463                         (*j)->create_item(*this, name);
464
465         return *get_item(items, name).value<RefPtr<NCT> >();
466 }
467
468 template<typename T>
469 CollectionItemType<T> &Collection::add_type()
470 {
471         CollectionItemType<T> *type = new CollectionItemType<T>;
472         types.push_back(type);
473         return *type;
474 }
475
476 } // namespace DataFile
477 } // namespace Msp
478
479 #endif