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