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