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