]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Prefer vector over list
[libs/datafile.git] / source / collection.h
1 #ifndef MSP_DATAFILE_COLLECTION_H_
2 #define MSP_DATAFILE_COLLECTION_H_
3
4 #include <msp/core/attributes.h>
5 #include <msp/core/maputils.h>
6 #include <msp/core/refptr.h>
7 #include "collectionsource.h"
8 #include "loader.h"
9 #include "meta.h"
10
11 /* XXX This file is a big mess with too many things in it.  However, the
12 dependencies between those things make it difficult to split up. */
13
14 namespace Msp {
15 namespace DataFile {
16
17 class CollectionItemTypeBase;
18
19 template<typename T>
20 class CollectionItemType;
21
22 /**
23 A collection of objects that can be loaded from a datafile.  Each object is
24 identified by a name, which must be unique across the entire collection.
25
26 While this class can be instantiated by itself and used for storing objects,
27 loading requires that a subclass defines the supported types.  See the add_type
28 method for details.
29
30 Collections can have sources for loading objects on demand.  Automatic loading
31 only works on a non-const Collection.  See class CollectionSource for details.
32
33 As a last resort, a fallback collection can be designated for loading items
34 that are not present.  Items retrieted from the fallback collection are shared
35 between the collections, and are only deleted when all collections in the chain
36 have been destroyed.
37 */
38 class Collection
39 {
40 public:
41         /**
42         Loads objects into a Collection.  Automatically picks up keywords from
43         defined item types.
44         */
45         class Loader: public DataFile::Loader
46         {
47                 template<typename T> friend class CollectionItemType;
48
49         private:
50                 Collection &coll;
51
52         public:
53                 Loader(Collection &);
54                 Collection &get_object() const { return coll; }
55         private:
56                 template<typename T, typename S>
57                 void item(const std::string &n)
58                 {
59                         RefPtr<T> it = new T;
60                         ItemLoader<T> ldr(*it, coll);
61                         load_sub_with(ldr);
62                         coll.add<S>(n, it.get());
63                         it.release();
64                 }
65         };
66
67         template<typename T, bool = NeedsCollection<typename T::Loader>::value>
68         class ItemLoader;
69
70 private:
71         typedef std::map<std::string, Variant> ItemMap;
72         typedef std::vector<CollectionItemTypeBase *> TypeList;
73         typedef std::vector<const CollectionSource *> SourceList;
74
75         TypeList types;
76         ItemMap items;
77         SourceList sources;
78         Collection *fallback;
79
80         Collection(const Collection &);
81         Collection &operator=(const Collection &);
82 public:
83         Collection();
84         virtual ~Collection();
85
86         /** Adds an object into the collection.  The name must not pre-exist.  The
87         collection takes ownership of the object. */
88         template<typename T>
89         void add(const std::string &name, T *item)
90         {
91                 if(!item)
92                         throw std::invalid_argument("Collection::add(item)");
93
94                 RefPtr<typename RemoveConst<T>::Type> ptr(item);
95                 try
96                 {
97                         insert_unique(items, name, ptr);
98                 }
99                 catch(...)
100                 {
101                         // Avoid deleting the object
102                         ptr.release();
103                         throw;
104                 }
105         }
106
107         /// Gets a typed object from the collection.
108         template<typename T>
109         T &get(const std::string &name) const
110         {
111                 return extract<typename RemoveConst<T>::Type>(get_item(items, name));
112         }
113
114         /** Gets a typed object from the collection.  If the name is not found,
115         automatic creation with the type's creator function (if defined) or from
116         sources (if present) is attempted. */
117         template<typename T>
118         T &get(const std::string &name)
119         {
120                 typedef typename RemoveConst<T>::Type NCT;
121                 return extract<NCT>(get_var(name, get_type<NCT>(name)));
122         }
123
124 private:
125         const Variant &get_var(const std::string &, const CollectionItemTypeBase *);
126
127         template<typename T>
128         T &extract(const Variant &var) const;
129
130         template<typename T>
131         std::list<T *> extract_list(const std::vector<const Variant *> &vars) const
132         {
133                 std::list<T *> result;
134                 for(std::vector<const Variant *>::const_iterator i=vars.begin(); i!=vars.end(); ++i)
135                         result.push_back(&extract<T>(**i));
136                 return result;
137         }
138
139         void gather_items(std::vector<const Variant *> *, std::list<std::string> *, const CollectionItemTypeBase &, bool) const;
140
141         template<typename T>
142         void gather_items(std::vector<const Variant *> *vars, std::list<std::string> *names, const CollectionItemTypeBase *type, bool include_sources) const
143         {
144                 if(type || (type = get_type<T>()))
145                         gather_items(vars, names, *type, include_sources);
146                 else
147                         gather_items(vars, names, CollectionItemType<T>(), false);
148         }
149
150 public:
151         /// Returns a list of the names of objects of one type in the collection.
152         template<typename T>
153         std::list<std::string> get_names() const
154         {
155                 std::list<std::string> names;
156                 gather_items<typename RemoveConst<T>::Type>(0, &names, 0, false);
157                 return names;
158         }
159
160         /** Returns a list of the names of objects of one type in the collection or
161         available from sources. */
162         template<typename T>
163         std::list<std::string> get_names()
164         {
165                 std::list<std::string> names;
166                 gather_items<typename RemoveConst<T>::Type>(0, &names, 0, true);
167                 return names;
168         }
169
170         /// Returns a list of objects of one type in the collection.
171         template<typename T>
172         std::list<T *> get_list() const
173         {
174                 std::vector<const Variant *> vars;
175                 gather_items<typename RemoveConst<T>::Type>(&vars, 0, 0, false);
176                 return extract_list<T>(vars);
177         }
178
179         /** Returns a list of objects of one type, loading them from sources if
180         necessary. */
181         template<typename T>
182         std::list<T *> get_list()
183         {
184                 CollectionItemTypeBase *type = get_type<typename RemoveConst<T>::Type>();
185                 if(type)
186                         load_items_from_sources(*type);
187
188                 std::vector<const Variant *> vars;
189                 gather_items<typename RemoveConst<T>::Type>(&vars, 0, type, true);
190                 return extract_list<T>(vars);
191         }
192
193 private:
194         unsigned get_status(const std::string &, const CollectionItemTypeBase &) const;
195
196         template<typename T>
197         unsigned get_status(const std::string &name) const
198         {
199                 // XXX Should go through all applicable types
200                 if(CollectionItemTypeBase *type = get_type<T>())
201                         return get_status(name, *type);
202
203                 ItemMap::const_iterator i = items.find(name);
204                 return (i!=items.end() && i->second.check_type<RefPtr<T> >());
205         }
206
207 public:
208         /// Checks whether a typed object exists in the collection.
209         template<typename T>
210         bool contains(const std::string &name) const
211         { return get_status<typename RemoveConst<T>::Type>(name)==1; }
212
213         /** Checks whether a typed object exists in the collection or is loadable
214         from a source. */
215         template<typename T>
216         bool contains(const std::string &name)
217         { return get_status<typename RemoveConst<T>::Type>(name)>0; }
218
219         /// Returns the name of an item in the collection.
220         template<typename T>
221         const std::string &get_name(T *d) const
222         {
223                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
224
225                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
226                         if(i->second.check_type<RPNCT>())
227                                 if(i->second.value<RPNCT>().get()==d)
228                                         return i->first;
229         
230                 // XXX Need better exception class
231                 throw std::runtime_error("Item not found in collection");
232         }
233
234 protected:
235         /** Adds a type to the collection.  The returned descriptor object reference
236         can be used to define how objects of that type can be loaded. */
237         template<typename T>
238         CollectionItemType<T> &add_type();
239
240         /** Returns a mutable reference to an existing type descriptor.  This can be
241         used to e.g. override the creator function of a type added by a base class. */
242         template<typename T>
243         CollectionItemType<T> &modify_type();
244
245 private:
246         /** Returns the descriptor for a type, or null if one isn't defined.  An
247         optional name can be given to prioritize matching types. */
248         template<typename T>
249         CollectionItemTypeBase *get_type(const std::string & = std::string()) const;
250
251         /// Gets a descriptor with the same type as another descriptor.
252         CollectionItemTypeBase *get_type(const CollectionItemTypeBase &) const;
253
254         /// Returns the descriptor for an item, or null if it's of an unknown type.
255         CollectionItemTypeBase *get_type_for_item(const Variant &) const;
256
257 protected:
258         /** Adds a source for automatically loading items.  Sources are consulted
259         in the order they are added. */
260         void add_source(const CollectionSource &);
261
262 public:
263         /** Opens a raw resource, without interpreting it as object data.  Null is
264         returned if no such file is found.  The caller must dispose of the returned
265         object when done with it. */
266         IO::Seekable *open_raw(const std::string &) const;
267
268 protected:
269         // Deprecated.  Use open_raw instead.
270         DEPRECATED IO::Seekable *open_from_sources(const std::string &n) { return open_raw(n); }
271
272 private:
273         void gather_names_from_sources(std::list<std::string> &, const CollectionItemTypeBase &) const;
274
275         void load_items_from_sources(const CollectionItemTypeBase &);
276
277 protected:
278         /** Sets a fallback collection, which will be consulted if an item is not
279         found. */
280         void set_fallback(Collection *);
281
282         Collection *get_fallback() const { return fallback; }
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 (dynamic_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 loadable
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 CollectionItemType<T> &Collection::modify_type()
498 {
499         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
500                 if(CollectionItemType<T> *t = dynamic_cast<CollectionItemType<T> *>(*j))
501                         return *t;
502
503         throw std::logic_error("type not found in collection");
504 }
505
506 template<typename T>
507 CollectionItemTypeBase *Collection::get_type(const std::string &name) const
508 {
509         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
510                 if(dynamic_cast<CollectionItemType<T> *>(*j))
511                         return *j;
512         CollectionItemTypeBase *type = 0;
513         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
514                 if((*j)->can_extract<T>())
515                 {
516                         if(!name.empty() && (*j)->match_name(name))
517                                 return *j;
518                         type = *j;
519                 }
520         return type;
521 }
522
523 } // namespace DataFile
524 } // namespace Msp
525
526 #endif