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