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