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