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