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