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