]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Remove some long-deprecated things
[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 private:
301         void gather_names_from_sources(std::list<std::string> &, const CollectionItemTypeBase &) const;
302
303         void load_items_from_sources(const CollectionItemTypeBase &);
304
305 protected:
306         /** Sets a fallback collection, which will be consulted if an item is not
307         found. */
308         void set_fallback(Collection *);
309
310         Collection *get_fallback() const { return fallback; }
311 };
312
313 template<typename T>
314 class Collection::ItemLoader<T, false>: public T::Loader
315 {
316 public:
317         ItemLoader(T &o, Collection &):
318                 T::Loader(o)
319         { }
320 };
321
322 template<typename T>
323 class Collection::ItemLoader<T, true>: public T::Loader
324 {
325 public:
326         ItemLoader(T &o, Collection &c):
327                 T::Loader(o, dynamic_cast<typename T::Loader::Collection &>(c))
328         { }
329 };
330
331
332 class CollectionItemTypeBase
333 {
334 protected:
335         struct ExtractorBase
336         {
337                 virtual ~ExtractorBase() { }
338         };
339
340         template<typename T>
341         struct Extractor: ExtractorBase
342         {
343                 virtual T &extract(const Variant &) const = 0;
344         };
345
346         std::string kwd;
347         std::vector<std::string> suffixes;
348         std::vector<ExtractorBase *> extractors;
349
350         CollectionItemTypeBase() { }
351 public:
352         virtual ~CollectionItemTypeBase();
353
354 protected:
355         void set_keyword(const std::string &);
356         void add_suffix(const std::string &);
357 public:
358         const std::string &get_keyword() const { return kwd; }
359         bool match_name(const std::string &) const;
360         virtual bool is_same_type(const CollectionItemTypeBase &) const = 0;
361         virtual bool check_item_type(const Variant &) const = 0;
362         virtual void add_to_loader(Collection::Loader &) const = 0;
363         virtual bool can_create() const = 0;
364         virtual void create_item(Collection &, const std::string &) const = 0;
365         virtual void load_item(Collection &, Parser &, const std::string &) const = 0;
366
367         template<typename T>
368         bool can_extract() const
369         {
370                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
371                         if(dynamic_cast<Extractor<T> *>(*i))
372                                 return true;
373                 return false;
374         }
375
376         template<typename T>
377         T *extract(const Variant &var) const
378         {
379                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
380                         if(Extractor<T> *ex = dynamic_cast<Extractor<T> *>(*i))
381                                 return &ex->extract(var);
382                 return 0;
383         }
384 };
385
386
387 /**
388 Describes a type of item that can be loaded by a Collection.  These are created
389 by Collection::add_type.
390 */
391 template<typename T>
392 class CollectionItemType: public CollectionItemTypeBase
393 {
394 private:
395         struct CreatorBase
396         {
397                 virtual ~CreatorBase() { }
398
399                 virtual T *create(Collection &, const std::string &) const = 0;
400         };
401
402         template<typename C>
403         struct Creator: CreatorBase
404         {
405                 typedef T *(C::*FuncPtr)(const std::string &);
406
407                 FuncPtr func;
408
409                 Creator(FuncPtr f): func(f) { }
410
411                 virtual T *create(Collection &coll, const std::string &name) const
412                 { return (dynamic_cast<C &>(coll).*func)(name); }
413         };
414
415         template<typename B>
416         struct Extractor: CollectionItemTypeBase::Extractor<B>
417         {
418                 virtual B &extract(const Variant &var) const
419                 { return *var.value<RefPtr<T> >(); }
420         };
421
422         CreatorBase *creat;
423
424 public:
425         CollectionItemType():
426                 creat(0)
427         { }
428
429         ~CollectionItemType()
430         {
431                 delete creat;
432         }
433
434         /** Sets a datafile keyword for this item type.  The Collection's loader
435         will accept a statement with this keyword and a single string argument - the
436         item's name. */
437         CollectionItemType &keyword(const std::string &k)
438         {
439                 set_keyword(k);
440                 return *this;
441         }
442
443         /** Adds a suffix that is used to match names when looking for loadable
444         objects.  There is no implied separator; a name matches if it ends with the
445         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
446         added as a suffix. */
447         CollectionItemType &suffix(const std::string &s)
448         {
449                 add_suffix(s);
450                 return *this;
451         }
452
453         /** Attaches a creator function to this item type.  If an item is not found
454         in the Collection, the creator function for its type is called to create it.
455         The function must be a member of the Collection subclass containing the
456         type.  It must return the created object, or null if it could not be
457         created.  It's also permissible to load the item via other means and then
458         return null. */
459         template<typename C>
460         CollectionItemType &creator(T *(C::*func)(const std::string &))
461         {
462                 delete creat;
463                 creat = new Creator<C>(func);
464                 return *this;
465         }
466
467         /** Makes items of this type available through a base class. */
468         template<typename B>
469         CollectionItemType &base()
470         {
471                 extractors.push_back(new Extractor<B>);
472                 return *this;
473         }
474
475         virtual bool is_same_type(const CollectionItemTypeBase &other) const
476         { return dynamic_cast<const CollectionItemType<T> *>(&other); }
477
478         virtual bool check_item_type(const Variant &var) const
479         { return var.check_type<RefPtr<T> >(); }
480
481         virtual void add_to_loader(Collection::Loader &) const
482         { }
483
484         virtual bool can_create() const
485         { return creat!=0; }
486
487         virtual void create_item(Collection &coll, const std::string &name) const
488         {
489                 if(!creat)
490                         throw std::runtime_error("no creator");
491                 T *obj = creat->create(coll, name);
492                 if(obj)
493                         coll.add(name, obj);
494         }
495
496         virtual void load_item(Collection &, Parser &, const std::string &) const
497         {
498                 throw std::runtime_error("this type cannot be loaded");
499         }
500 };
501
502
503 template<typename T>
504 class LoadableCollectionItemType: public CollectionItemType<T>
505 {
506 public:
507         virtual void add_to_loader(Collection::Loader &loader) const
508         { loader.add(this->kwd, &Collection::Loader::item<T, T>); }
509
510         virtual void load_item(Collection &coll, Parser &parser, const std::string &name) const
511         {
512                 RefPtr<T> obj = new T;
513                 Collection::ItemLoader<T> ldr(*obj, coll);
514                 ldr.load(parser);
515                 coll.add(name, obj.get());
516                 obj.release();
517         }
518 };
519
520
521 template<typename T>
522 T &Collection::extract(const Variant &var) const
523 {
524         if(!var.check_type<RefPtr<T> >())
525                 if(CollectionItemTypeBase *type = get_type_for_item(var))
526                         if(T *item = type->extract<T>(var))
527                                 return *item;
528
529         return *var.value<RefPtr<T> >();
530 }
531
532 template<typename T>
533 typename CollectionItemTypeChooser<T>::Type &Collection::add_type()
534 {
535         typename CollectionItemTypeChooser<T>::Type *type = new typename CollectionItemTypeChooser<T>::Type;
536         types.push_back(type);
537         return *type;
538 }
539
540 template<typename T>
541 typename CollectionItemTypeChooser<T>::Type &Collection::modify_type()
542 {
543         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
544                 if(CollectionItemType<T> *t = dynamic_cast<CollectionItemType<T> *>(*j))
545                         return *t;
546
547         throw std::logic_error("type not found in collection");
548 }
549
550 template<typename T>
551 CollectionItemTypeBase *Collection::get_type(const std::string &name) const
552 {
553         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
554                 if(dynamic_cast<CollectionItemType<T> *>(*j))
555                         return *j;
556         CollectionItemTypeBase *type = 0;
557         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
558                 if((*j)->can_extract<T>())
559                 {
560                         if(!name.empty() && (*j)->match_name(name))
561                                 return *j;
562                         type = *j;
563                 }
564         return type;
565 }
566
567 } // namespace DataFile
568 } // namespace Msp
569
570 #endif