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