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