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