]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Chain get_status to fallback collection if one is defined
[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         /// Gets a descriptor with the same type as another descriptor.
259         CollectionItemTypeBase *get_type(const CollectionItemTypeBase &) const;
260
261         /// Returns the descriptor for an item, or null if it's of an unknown type.
262         CollectionItemTypeBase *get_type_for_item(const Variant &) const;
263
264 protected:
265         /** Adds a source for automatically loading items.  Sources are consulted
266         in the order they are added. */
267         void add_source(CollectionSource &);
268
269 public:
270         /** Opens a raw resource, without interpreting it as object data.  Null is
271         returned if no such file is found.  The caller must dispose of the returned
272         object when done with it. */
273         IO::Seekable *open_raw(const std::string &) const;
274
275 protected:
276         IO::Seekable *open_from_sources(const std::string &n) { return open_raw(n); }
277
278 private:
279         void gather_names_from_sources(std::list<std::string> &, const CollectionItemTypeBase &) const;
280
281         void load_items_from_sources(const CollectionItemTypeBase &);
282
283 protected:
284         /** Sets a fallback collection, which will be consulted if an item is not
285         found. */
286         void set_fallback(Collection *);
287 };
288
289 template<typename T>
290 class Collection::ItemLoader<T, false>: public T::Loader
291 {
292 public:
293         ItemLoader(T &o, Collection &):
294                 T::Loader(o)
295         { }
296 };
297
298 template<typename T>
299 class Collection::ItemLoader<T, true>: public T::Loader
300 {
301 public:
302         ItemLoader(T &o, Collection &c):
303                 T::Loader(o, dynamic_cast<typename T::Loader::Collection &>(c))
304         { }
305 };
306
307
308 class CollectionItemTypeBase
309 {
310 protected:
311         struct ExtractorBase
312         {
313                 virtual ~ExtractorBase() { }
314         };
315
316         template<typename T>
317         struct Extractor: ExtractorBase
318         {
319                 virtual T &extract(const Variant &) const = 0;
320         };
321
322         std::string kwd;
323         std::vector<std::string> suffixes;
324         std::vector<ExtractorBase *> extractors;
325
326         CollectionItemTypeBase() { }
327 public:
328         virtual ~CollectionItemTypeBase();
329
330         void set_keyword(const std::string &);
331         const std::string &get_keyword() const { return kwd; }
332         void add_suffix(const std::string &);
333         bool match_name(const std::string &) const;
334         virtual bool is_same_type(const CollectionItemTypeBase &) const = 0;
335         virtual bool check_item_type(const Variant &) const = 0;
336         virtual void add_to_loader(Collection::Loader &) const = 0;
337         virtual bool can_create() const = 0;
338         virtual void create_item(Collection &, const std::string &) const = 0;
339         virtual void load_item(Collection &, Parser &, const std::string &) const = 0;
340
341         template<typename T>
342         bool can_extract() const
343         {
344                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
345                         if(dynamic_cast<Extractor<T> *>(*i))
346                                 return true;
347                 return false;
348         }
349
350         template<typename T>
351         T *extract(const Variant &var) const
352         {
353                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
354                         if(Extractor<T> *ex = dynamic_cast<Extractor<T> *>(*i))
355                                 return &ex->extract(var);
356                 return 0;
357         }
358 };
359
360
361 /**
362 Describes a type of item that can be loaded by a Collection.  These are created
363 by Collection::add_type.
364 */
365 template<typename T>
366 class CollectionItemType: public CollectionItemTypeBase
367 {
368 private:
369         struct CreatorBase
370         {
371                 virtual ~CreatorBase() { }
372
373                 virtual T *create(Collection &, const std::string &) const = 0;
374         };
375
376         template<typename C>
377         struct Creator: CreatorBase
378         {
379                 typedef T *(C::*FuncPtr)(const std::string &);
380
381                 FuncPtr func;
382
383                 Creator(FuncPtr f): func(f) { }
384
385                 virtual T *create(Collection &coll, const std::string &name) const
386                 { return (dynamic_cast<C &>(coll).*func)(name); }
387         };
388
389         template<typename B>
390         struct Extractor: CollectionItemTypeBase::Extractor<B>
391         {
392                 virtual B &extract(const Variant &var) const
393                 { return *var.value<RefPtr<T> >(); }
394         };
395
396         CreatorBase *creat;
397
398 public:
399         CollectionItemType():
400                 creat(0)
401         { }
402
403         ~CollectionItemType()
404         {
405                 delete creat;
406         }
407
408         /** Sets a datafile keyword for this item type.  The Collection's loader
409         will accept a statement with this keyword and a single string argument - the
410         item's name. */
411         CollectionItemType &keyword(const std::string &k)
412         {
413                 set_keyword(k);
414                 return *this;
415         }
416
417         /** Adds a suffix that is used to match names when looking for future
418         objects.  There is no implied separator; a name matches if it ends with the
419         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
420         added as a suffix. */
421         CollectionItemType &suffix(const std::string &s)
422         {
423                 add_suffix(s);
424                 return *this;
425         }
426
427         /** Attaches a creator function to this item type.  If an item is not found
428         in the Collection, the creator function for its type is called to create it.
429         The function must be a member of the Collection subclass containing the
430         type.  It must return the created object, or null if it could not be
431         created.  It's also permissible to load the item via other means and then
432         return null. */
433         template<typename C>
434         CollectionItemType &creator(T *(C::*func)(const std::string &))
435         {
436                 delete creat;
437                 creat = new Creator<C>(func);
438                 return *this;
439         }
440
441         /** Makes items of this type available through a base class. */
442         template<typename B>
443         CollectionItemType &base()
444         {
445                 extractors.push_back(new Extractor<B>);
446                 return *this;
447         }
448
449         virtual bool is_same_type(const CollectionItemTypeBase &other) const
450         { return dynamic_cast<const CollectionItemType<T> *>(&other); }
451
452         virtual bool check_item_type(const Variant &var) const
453         { return var.check_type<RefPtr<T> >(); }
454
455         virtual void add_to_loader(Collection::Loader &loader) const
456         { loader.add(kwd, &Collection::Loader::item<T, T>); }
457
458         virtual bool can_create() const
459         { return creat!=0; }
460
461         virtual void create_item(Collection &coll, const std::string &name) const
462         {
463                 if(!creat)
464                         throw std::runtime_error("no creator");
465                 T *obj = creat->create(coll, name);
466                 if(obj)
467                         coll.add(name, obj);
468         }
469
470         virtual void load_item(Collection &coll, Parser &parser, const std::string &name) const
471         {
472                 RefPtr<T> obj = new T;
473                 Collection::ItemLoader<T> ldr(*obj, coll);
474                 ldr.load(parser);
475                 coll.add(name, obj.get());
476                 obj.release();
477         }
478 };
479
480
481 template<typename T>
482 T &Collection::extract(const Variant &var) const
483 {
484         if(!var.check_type<RefPtr<T> >())
485                 if(CollectionItemTypeBase *type = get_type_for_item(var))
486                         if(T *item = type->extract<T>(var))
487                                 return *item;
488
489         return *var.value<RefPtr<T> >();
490 }
491
492 template<typename T>
493 CollectionItemType<T> &Collection::add_type()
494 {
495         CollectionItemType<T> *type = new CollectionItemType<T>;
496         types.push_back(type);
497         return *type;
498 }
499
500 template<typename T>
501 CollectionItemTypeBase *Collection::get_type() const
502 {
503         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
504                 if(dynamic_cast<CollectionItemType<T> *>(*j))
505                         return *j;
506         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
507                 if((*j)->can_extract<T>())
508                         return *j;
509         return 0;
510 }
511
512 } // namespace DataFile
513 } // namespace Msp
514
515 #endif