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