]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Use metaprogramming constructs from std
[libs/datafile.git] / source / collection.h
1 #ifndef MSP_DATAFILE_COLLECTION_H_
2 #define MSP_DATAFILE_COLLECTION_H_
3
4 #include <type_traits>
5 #include <msp/core/attributes.h>
6 #include <msp/core/maputils.h>
7 #include <msp/core/noncopyable.h>
8 #include <msp/core/refptr.h>
9 #include "collectionsource.h"
10 #include "loader.h"
11 #include "meta.h"
12
13 /* XXX This file is a big mess with too many things in it.  However, the
14 dependencies between those things make it difficult to split up. */
15
16 namespace Msp {
17 namespace DataFile {
18
19 class CollectionItemTypeBase;
20
21 template<typename T>
22 class CollectionItemType;
23
24 template<typename T>
25 class LoadableCollectionItemType;
26
27 template<typename T, bool = HasLoader<T>::value>
28 struct CollectionItemTypeChooser;
29
30 template<typename T>
31 struct CollectionItemTypeChooser<T, true>
32 { typedef LoadableCollectionItemType<T> Type; };
33
34 template<typename T>
35 struct CollectionItemTypeChooser<T, false>
36 { typedef CollectionItemType<T> Type; };
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 As a last resort, a fallback collection can be designated for loading items
50 that 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: private NonCopyable
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 LoadableCollectionItemType;
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::vector<CollectionItemTypeBase *> TypeList;
89         typedef std::vector<const CollectionSource *> SourceList;
90
91         TypeList types;
92         ItemMap items;
93         SourceList sources;
94         Collection *fallback;
95
96 public:
97         Collection();
98         virtual ~Collection();
99
100         /** Adds an object into the collection.  The name must not pre-exist.  The
101         collection takes ownership of the object. */
102         template<typename T>
103         void add(const std::string &name, T *item)
104         {
105                 if(!item)
106                         throw std::invalid_argument("Collection::add(item)");
107
108                 typedef typename std::remove_cv<T>::type NCT;
109                 RefPtr<NCT> ptr(item);
110                 try
111                 {
112                         add_var(name, get_type<NCT>(name), ptr);
113                 }
114                 catch(...)
115                 {
116                         // Avoid deleting the object
117                         ptr.release();
118                         throw;
119                 }
120         }
121
122         /// Gets a typed object from the collection.
123         template<typename T>
124         T &get(const std::string &name) const
125         {
126                 return extract<typename std::remove_cv<T>::type>(get_item(items, name));
127         }
128
129         /** Gets a typed object from the collection.  If the name is not found,
130         automatic creation with the type's creator function (if defined) or from
131         sources (if present) is attempted. */
132         template<typename T>
133         T &get(const std::string &name)
134         {
135                 typedef typename std::remove_cv<T>::type NCT;
136                 return extract<NCT>(get_var(name, get_type<NCT>(name)));
137         }
138
139         /** Finds a typed object in the collection.  Returns null if the name does
140         not exist.  Throws if the name exists but the object is of an incorrect
141         type. */
142         template<typename T>
143         T *find(const std::string &name) const
144         {
145                 ItemMap::const_iterator i = items.find(name);
146                 return (i!=items.end() ? extract<typename std::remove_cv<T>::type>(i->second) : 0);
147         }
148
149         template<typename T>
150         T *find(const std::string &name)
151         {
152                 typedef typename std::remove_cv<T>::type NCT;
153                 const Variant *var = find_var(name, get_type<NCT>(name));
154                 return (var ? &extract<NCT>(*var) : 0);
155         }
156
157 private:
158         void add_var(const std::string &, const CollectionItemTypeBase *, const Variant &);
159         const Variant &get_var(const std::string &, const CollectionItemTypeBase *);
160         const Variant *find_var(const std::string &, const CollectionItemTypeBase *);
161
162         template<typename T>
163         T &extract(const Variant &var) const;
164
165         template<typename T>
166         std::list<T *> extract_list(const std::vector<const Variant *> &vars) const
167         {
168                 std::list<T *> result;
169                 for(std::vector<const Variant *>::const_iterator i=vars.begin(); i!=vars.end(); ++i)
170                         result.push_back(&extract<T>(**i));
171                 return result;
172         }
173
174         void gather_items(std::vector<const Variant *> *, std::list<std::string> *, const CollectionItemTypeBase &, bool) const;
175
176         template<typename T>
177         void gather_items(std::vector<const Variant *> *vars, std::list<std::string> *names, const CollectionItemTypeBase *type, bool include_sources) const
178         {
179                 if(type || (type = get_type<T>()))
180                         gather_items(vars, names, *type, include_sources);
181                 else
182                         gather_items(vars, names, CollectionItemType<T>(), false);
183         }
184
185 public:
186         /// Returns a list of the names of objects of one type in the collection.
187         template<typename T>
188         std::list<std::string> get_names() const
189         {
190                 std::list<std::string> names;
191                 gather_items<typename std::remove_cv<T>::type>(0, &names, 0, false);
192                 return names;
193         }
194
195         /** Returns a list of the names of objects of one type in the collection or
196         available from sources. */
197         template<typename T>
198         std::list<std::string> get_names()
199         {
200                 std::list<std::string> names;
201                 gather_items<typename std::remove_cv<T>::type>(0, &names, 0, true);
202                 return names;
203         }
204
205         /// Returns a list of objects of one type in the collection.
206         template<typename T>
207         std::list<T *> get_list() const
208         {
209                 std::vector<const Variant *> vars;
210                 gather_items<typename std::remove_cv<T>::type>(&vars, 0, 0, false);
211                 return extract_list<T>(vars);
212         }
213
214         /** Returns a list of objects of one type, loading them from sources if
215         necessary. */
216         template<typename T>
217         std::list<T *> get_list()
218         {
219                 CollectionItemTypeBase *type = get_type<typename std::remove_cv<T>::type>();
220                 if(type)
221                         load_items_from_sources(*type);
222
223                 std::vector<const Variant *> vars;
224                 gather_items<typename std::remove_cv<T>::type>(&vars, 0, type, true);
225                 return extract_list<T>(vars);
226         }
227
228 private:
229         unsigned get_status(const std::string &, const CollectionItemTypeBase &) const;
230
231         template<typename T>
232         unsigned get_status(const std::string &name) const
233         {
234                 // XXX Should go through all applicable types
235                 if(CollectionItemTypeBase *type = get_type<T>())
236                         return get_status(name, *type);
237
238                 ItemMap::const_iterator i = items.find(name);
239                 return (i!=items.end() && i->second.check_type<RefPtr<T> >());
240         }
241
242 public:
243         /// Checks whether a typed object exists in the collection.
244         template<typename T>
245         bool contains(const std::string &name) const
246         { return get_status<typename std::remove_cv<T>::type>(name)==1; }
247
248         /** Checks whether a typed object exists in the collection or is loadable
249         from a source. */
250         template<typename T>
251         bool contains(const std::string &name)
252         { return get_status<typename std::remove_cv<T>::type>(name)>0; }
253
254         /// Returns the name of an item in the collection.
255         template<typename T>
256         const std::string &get_name(T *d) const
257         {
258                 typedef RefPtr<typename std::remove_cv<T>::type> RPNCT;
259
260                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
261                         if(i->second.check_type<RPNCT>())
262                                 if(i->second.value<RPNCT>().get()==d)
263                                         return i->first;
264         
265                 // XXX Need better exception class
266                 throw std::runtime_error("Item not found in collection");
267         }
268
269 protected:
270         /** Adds a type to the collection.  The returned descriptor object reference
271         can be used to define how objects of that type can be loaded. */
272         template<typename T>
273         typename CollectionItemTypeChooser<T>::Type &add_type();
274
275         /** Returns a mutable reference to an existing type descriptor.  This can be
276         used to e.g. override the creator function of a type added by a base class. */
277         template<typename T>
278         typename CollectionItemTypeChooser<T>::Type &modify_type();
279
280 private:
281         /** Returns the descriptor for a type, or null if one isn't defined.  An
282         optional name can be given to prioritize matching types. */
283         template<typename T>
284         CollectionItemTypeBase *get_type(const std::string & = std::string()) const;
285
286         /// Gets a descriptor with the same type as another descriptor.
287         CollectionItemTypeBase *get_type(const CollectionItemTypeBase &) const;
288
289         /// Returns the descriptor for an item, or null if it's of an unknown type.
290         CollectionItemTypeBase *get_type_for_item(const Variant &) const;
291
292 protected:
293         /** Adds a source for automatically loading items.  Sources are consulted
294         in the order they are added. */
295         void add_source(const CollectionSource &);
296
297 public:
298         /** Opens a raw resource, without interpreting it as object data.  Null is
299         returned if no such file is found.  The caller must dispose of the returned
300         object when done with it. */
301         IO::Seekable *open_raw(const std::string &) const;
302
303 private:
304         void gather_names_from_sources(std::list<std::string> &, const CollectionItemTypeBase &) const;
305
306         void load_items_from_sources(const CollectionItemTypeBase &);
307
308 protected:
309         /** Sets a fallback collection, which will be consulted if an item is not
310         found. */
311         void set_fallback(Collection *);
312
313         Collection *get_fallback() const { return fallback; }
314 };
315
316 template<typename T>
317 class Collection::ItemLoader<T, false>: public T::Loader
318 {
319 public:
320         ItemLoader(T &o, Collection &):
321                 T::Loader(o)
322         { }
323 };
324
325 template<typename T>
326 class Collection::ItemLoader<T, true>: public T::Loader
327 {
328 public:
329         ItemLoader(T &o, Collection &c):
330                 T::Loader(o, dynamic_cast<typename T::Loader::Collection &>(c))
331         { }
332 };
333
334
335 class CollectionItemTypeBase
336 {
337 protected:
338         struct ExtractorBase
339         {
340                 virtual ~ExtractorBase() { }
341         };
342
343         template<typename T>
344         struct Extractor: ExtractorBase
345         {
346                 virtual T &extract(const Variant &) const = 0;
347         };
348
349         std::string kwd;
350         std::vector<std::string> suffixes;
351         std::vector<ExtractorBase *> extractors;
352
353         CollectionItemTypeBase() { }
354 public:
355         virtual ~CollectionItemTypeBase();
356
357 protected:
358         void set_keyword(const std::string &);
359         void add_suffix(const std::string &);
360 public:
361         const std::string &get_keyword() const { return kwd; }
362         bool match_name(const std::string &) const;
363         virtual bool is_same_type(const CollectionItemTypeBase &) const = 0;
364         virtual bool check_item_type(const Variant &) const = 0;
365         virtual void add_to_loader(Collection::Loader &) const = 0;
366         virtual bool can_create() const = 0;
367         virtual void create_item(Collection &, const std::string &) const = 0;
368         virtual void load_item(Collection &, Parser &, const std::string &) const = 0;
369         virtual void notify_item(Collection &, const std::string &, const Variant &) const = 0;
370
371         template<typename T>
372         bool can_extract() const
373         {
374                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
375                         if(dynamic_cast<Extractor<T> *>(*i))
376                                 return true;
377                 return false;
378         }
379
380         template<typename T>
381         T *extract(const Variant &var) const
382         {
383                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
384                         if(Extractor<T> *ex = dynamic_cast<Extractor<T> *>(*i))
385                                 return &ex->extract(var);
386                 return 0;
387         }
388 };
389
390
391 /**
392 Describes a type of item that can be loaded by a Collection.  These are created
393 by Collection::add_type.
394 */
395 template<typename T>
396 class CollectionItemType: public CollectionItemTypeBase
397 {
398 private:
399         struct CreatorBase
400         {
401                 virtual ~CreatorBase() { }
402
403                 virtual T *create(Collection &, const std::string &) const = 0;
404         };
405
406         template<typename C>
407         struct Creator: CreatorBase
408         {
409                 typedef T *(C::*FuncPtr)(const std::string &);
410
411                 FuncPtr func;
412
413                 Creator(FuncPtr f): func(f) { }
414
415                 virtual T *create(Collection &coll, const std::string &name) const
416                 { return (dynamic_cast<C &>(coll).*func)(name); }
417         };
418
419         struct NotifyeeBase
420         {
421                 virtual ~NotifyeeBase() { }
422
423                 virtual void notify(Collection &, const std::string &, T &) const = 0;
424         };
425
426         template<typename C>
427         struct Notifyee: NotifyeeBase
428         {
429                 typedef void (C::*FuncPtr)(const std::string &, T &);
430
431                 FuncPtr func;
432
433                 Notifyee(FuncPtr f): func(f) { }
434
435                 virtual void notify(Collection &coll, const std::string &name, T &item) const
436                 { (dynamic_cast<C &>(coll).*func)(name, item); }
437         };
438
439         template<typename B>
440         struct Extractor: CollectionItemTypeBase::Extractor<B>
441         {
442                 virtual B &extract(const Variant &var) const
443                 { return *var.value<RefPtr<T> >(); }
444         };
445
446         CreatorBase *creat;
447         std::vector<NotifyeeBase *> notif;
448
449 public:
450         CollectionItemType():
451                 creat(0)
452         { }
453
454         ~CollectionItemType()
455         {
456                 delete creat;
457                 for(typename std::vector<NotifyeeBase *>::const_iterator i=notif.begin(); i!=notif.end(); ++i)
458                         delete *i;
459         }
460
461         /** Sets a datafile keyword for this item type.  The Collection's loader
462         will accept a statement with this keyword and a single string argument - the
463         item's name. */
464         CollectionItemType &keyword(const std::string &k)
465         {
466                 set_keyword(k);
467                 return *this;
468         }
469
470         /** Adds a suffix that is used to match names when looking for loadable
471         objects.  There is no implied separator; a name matches if it ends with the
472         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
473         added as a suffix. */
474         CollectionItemType &suffix(const std::string &s)
475         {
476                 add_suffix(s);
477                 return *this;
478         }
479
480         /** Attaches a creator function to this item type.  If an item is not found
481         in the Collection, the creator function for its type is called to create it.
482         The function must be a member of the Collection subclass containing the
483         type.  It must return the created object, or null if it could not be
484         created.  It's also permissible to load the item via other means and then
485         return null. */
486         template<typename C>
487         CollectionItemType &creator(T *(C::*func)(const std::string &))
488         {
489                 delete creat;
490                 creat = new Creator<C>(func);
491                 return *this;
492         }
493
494         /** Makes items of this type available through a base class. */
495         template<typename B>
496         CollectionItemType &base()
497         {
498                 extractors.push_back(new Extractor<B>);
499                 return *this;
500         }
501
502         template<typename C>
503         CollectionItemType &notify(void (C::*func)(const std::string &, T &))
504         {
505                 notif.push_back(new Notifyee<C>(func));
506                 return *this;
507         }
508
509         virtual bool is_same_type(const CollectionItemTypeBase &other) const
510         { return dynamic_cast<const CollectionItemType<T> *>(&other); }
511
512         virtual bool check_item_type(const Variant &var) const
513         { return var.check_type<RefPtr<T> >(); }
514
515         virtual void add_to_loader(Collection::Loader &) const
516         { }
517
518         virtual bool can_create() const
519         { return creat!=0; }
520
521         virtual void create_item(Collection &coll, const std::string &name) const
522         {
523                 if(!creat)
524                         throw std::runtime_error("no creator");
525                 T *obj = creat->create(coll, name);
526                 if(obj)
527                         coll.add(name, obj);
528         }
529
530         virtual void load_item(Collection &, Parser &, const std::string &) const
531         {
532                 throw std::runtime_error("this type cannot be loaded");
533         }
534
535         virtual void notify_item(Collection &coll, const std::string &name, const Variant &var) const
536         {
537                 RefPtr<T> obj = var.value<RefPtr<T> >();
538                 for(typename std::vector<NotifyeeBase *>::const_iterator i=notif.begin(); i!=notif.end(); ++i)
539                         (*i)->notify(coll, name, *obj);
540         }
541 };
542
543
544 template<typename T>
545 class LoadableCollectionItemType: public CollectionItemType<T>
546 {
547 public:
548         virtual void add_to_loader(Collection::Loader &loader) const
549         { loader.add(this->kwd, &Collection::Loader::item<T, T>); }
550
551         virtual void load_item(Collection &coll, Parser &parser, const std::string &name) const
552         {
553                 RefPtr<T> obj = new T;
554                 Collection::ItemLoader<T> ldr(*obj, coll);
555                 ldr.load(parser);
556                 coll.add(name, obj.get());
557                 obj.release();
558         }
559 };
560
561
562 template<typename T>
563 T &Collection::extract(const Variant &var) const
564 {
565         if(!var.check_type<RefPtr<T> >())
566                 if(CollectionItemTypeBase *type = get_type_for_item(var))
567                         if(T *item = type->extract<T>(var))
568                                 return *item;
569
570         return *var.value<RefPtr<T> >();
571 }
572
573 template<typename T>
574 typename CollectionItemTypeChooser<T>::Type &Collection::add_type()
575 {
576         typename CollectionItemTypeChooser<T>::Type *type = new typename CollectionItemTypeChooser<T>::Type;
577         types.push_back(type);
578         return *type;
579 }
580
581 template<typename T>
582 typename CollectionItemTypeChooser<T>::Type &Collection::modify_type()
583 {
584         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
585                 if(CollectionItemType<T> *t = dynamic_cast<CollectionItemType<T> *>(*j))
586                         return *t;
587
588         throw std::logic_error("type not found in collection");
589 }
590
591 template<typename T>
592 CollectionItemTypeBase *Collection::get_type(const std::string &name) const
593 {
594         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
595                 if(dynamic_cast<CollectionItemType<T> *>(*j))
596                         return *j;
597         CollectionItemTypeBase *type = 0;
598         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
599                 if((*j)->can_extract<T>())
600                 {
601                         if(!name.empty() && (*j)->match_name(name))
602                                 return *j;
603                         type = *j;
604                 }
605         return type;
606 }
607
608 } // namespace DataFile
609 } // namespace Msp
610
611 #endif