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