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