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