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