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