]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Rework the list and containment queries
[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 protected:
79         template<typename T, bool = NeedsCollection<typename T::Loader>::value>
80         class ItemLoader;
81
82 private:
83         typedef std::map<std::string, Variant> ItemMap;
84         typedef std::list<CollectionItemTypeBase *> TypeList;
85         typedef std::list<CollectionSource *> SourceList;
86
87         TypeList types;
88         ItemMap items;
89         SourceList sources;
90
91         Collection(const Collection &);
92         Collection &operator=(const Collection &);
93 public:
94         Collection() { }
95         virtual ~Collection();
96
97         /** Adds an object into the collection.  The name must not pre-exist.  The
98         collection takes ownership of the object. */
99         template<typename T>
100         void add(const std::string &name, T *item)
101         {
102                 if(!item)
103                         throw std::invalid_argument("Collection::add(item)");
104
105                 RefPtr<typename RemoveConst<T>::Type> ptr(item);
106                 try
107                 {
108                         insert_unique(items, name, ptr);
109                 }
110                 catch(...)
111                 {
112                         // Avoid deleting the object
113                         ptr.release();
114                         throw;
115                 }
116         }
117
118         /// Gets a typed object from the collection.
119         template<typename T>
120         T &get(const std::string &name) const
121         {
122                 return extract<T>(get_item(items, name));
123         }
124
125         /** Gets a typed object from the collection.  If the name is not found,
126         automatic creation with the type's creator function (if defined) or from
127         sources (if present) is attempted. */
128         template<typename T>
129         T &get(const std::string &name)
130         {
131                 return extract<T>(get_var(name, get_type<T>()));
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         /// Returns the descriptor for a type, or null if one isn't defined.
251         template<typename T>
252         CollectionItemTypeBase *get_type() const;
253
254         /// Returns the descriptor for an item, or null if it's of an unknown type.
255         CollectionItemTypeBase *get_type_for_item(const Variant &) const;
256
257         void add_source(CollectionSource &);
258
259         void gather_names_from_sources(std::list<std::string> &, const CollectionItemTypeBase &) const;
260
261         void load_items_from_sources(const CollectionItemTypeBase &);
262 };
263
264 template<typename T>
265 class Collection::ItemLoader<T, false>: public T::Loader
266 {
267 public:
268         ItemLoader(T &o, Collection &):
269                 T::Loader(o)
270         { }
271 };
272
273 template<typename T>
274 class Collection::ItemLoader<T, true>: public T::Loader
275 {
276 public:
277         ItemLoader(T &o, Collection &c):
278                 T::Loader(o, dynamic_cast<typename T::Loader::Collection &>(c))
279         { }
280 };
281
282
283 class CollectionItemTypeBase
284 {
285 protected:
286         struct ExtractorBase
287         {
288                 virtual ~ExtractorBase() { }
289         };
290
291         template<typename T>
292         struct Extractor: ExtractorBase
293         {
294                 virtual T &extract(const Variant &) const = 0;
295         };
296
297         std::string kwd;
298         std::vector<std::string> suffixes;
299         std::vector<ExtractorBase *> extractors;
300
301         CollectionItemTypeBase() { }
302 public:
303         virtual ~CollectionItemTypeBase();
304
305         void set_keyword(const std::string &);
306         const std::string &get_keyword() const { return kwd; }
307         void add_suffix(const std::string &);
308         bool match_name(const std::string &) const;
309         virtual bool check_item_type(const Variant &) const = 0;
310         virtual void add_to_loader(Collection::Loader &) const = 0;
311         virtual bool can_create() const = 0;
312         virtual void create_item(Collection &, const std::string &) const = 0;
313         virtual void load_item(Collection &, Parser &, const std::string &) const = 0;
314
315         template<typename T>
316         bool can_extract() const
317         {
318                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
319                         if(dynamic_cast<Extractor<T> *>(*i))
320                                 return true;
321                 return false;
322         }
323
324         template<typename T>
325         T *extract(const Variant &var) const
326         {
327                 for(std::vector<ExtractorBase *>::const_iterator i=extractors.begin(); i!=extractors.end(); ++i)
328                         if(Extractor<T> *ex = dynamic_cast<Extractor<T> *>(*i))
329                                 return &ex->extract(var);
330                 return 0;
331         }
332 };
333
334
335 /**
336 Describes a type of item that can be loaded by a Collection.  These are created
337 by Collection::add_type.
338 */
339 template<typename T>
340 class CollectionItemType: public CollectionItemTypeBase
341 {
342 private:
343         struct CreatorBase
344         {
345                 virtual ~CreatorBase() { }
346
347                 virtual T *create(Collection &, const std::string &) const = 0;
348         };
349
350         template<typename C>
351         struct Creator: CreatorBase
352         {
353                 typedef T *(C::*FuncPtr)(const std::string &);
354
355                 FuncPtr func;
356
357                 Creator(FuncPtr f): func(f) { }
358
359                 virtual T *create(Collection &coll, const std::string &name) const
360                 { return (static_cast<C &>(coll).*func)(name); }
361         };
362
363         template<typename B>
364         struct Extractor: CollectionItemTypeBase::Extractor<B>
365         {
366                 virtual B &extract(const Variant &var) const
367                 { return *var.value<RefPtr<T> >(); }
368         };
369
370         CreatorBase *creat;
371
372 public:
373         CollectionItemType():
374                 creat(0)
375         { }
376
377         ~CollectionItemType()
378         {
379                 delete creat;
380         }
381
382         /** Sets a datafile keyword for this item type.  The Collection's loader
383         will accept a statement with this keyword and a single string argument - the
384         item's name. */
385         CollectionItemType &keyword(const std::string &k)
386         {
387                 set_keyword(k);
388                 return *this;
389         }
390
391         /** Adds a suffix that is used to match names when looking for future
392         objects.  There is no implied separator; a name matches if it ends with the
393         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
394         added as a suffix. */
395         CollectionItemType &suffix(const std::string &s)
396         {
397                 add_suffix(s);
398                 return *this;
399         }
400
401         /** Attaches a creator function to this item type.  If an item is not found
402         in the Collection, the creator function for its type is called to create it.
403         The function must be a member of the Collection subclass containing the
404         type.  It must return the created object, or null if it could not be
405         created.  It's also permissible to load the item via other means and then
406         return null. */
407         template<typename C>
408         CollectionItemType &creator(T *(C::*func)(const std::string &))
409         {
410                 delete creat;
411                 creat = new Creator<C>(func);
412                 return *this;
413         }
414
415         /** Makes items of this type available through a base class. */
416         template<typename B>
417         CollectionItemType &base()
418         {
419                 extractors.push_back(new Extractor<B>);
420                 return *this;
421         }
422
423         virtual bool check_item_type(const Variant &var) const
424         { return var.check_type<RefPtr<T> >(); }
425
426         virtual void add_to_loader(Collection::Loader &loader) const
427         { loader.add(kwd, &Collection::Loader::item<T, T>); }
428
429         virtual bool can_create() const
430         { return creat!=0; }
431
432         virtual void create_item(Collection &coll, const std::string &name) const
433         {
434                 if(!creat)
435                         throw std::runtime_error("no creator");
436                 T *obj = creat->create(coll, name);
437                 if(obj)
438                         coll.add(name, obj);
439         }
440
441         virtual void load_item(Collection &coll, Parser &parser, const std::string &name) const
442         {
443                 RefPtr<T> obj = new T;
444                 Collection::ItemLoader<T> ldr(*obj, coll);
445                 ldr.load(parser);
446                 coll.add(name, obj.get());
447                 obj.release();
448         }
449 };
450
451
452 template<typename T>
453 T &Collection::extract(const Variant &var) const
454 {
455         typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
456
457         if(!var.check_type<RPNCT>())
458                 if(CollectionItemTypeBase *type = get_type_for_item(var))
459                         if(T *item = type->extract<T>(var))
460                                 return *item;
461
462         return *var.value<RPNCT>();
463 }
464
465 template<typename T>
466 CollectionItemType<T> &Collection::add_type()
467 {
468         CollectionItemType<T> *type = new CollectionItemType<T>;
469         types.push_back(type);
470         return *type;
471 }
472
473 template<typename T>
474 CollectionItemTypeBase *Collection::get_type() const
475 {
476         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
477                 if(dynamic_cast<CollectionItemType<typename RemoveConst<T>::Type> *>(*j))
478                         return *j;
479         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
480                 if((*j)->can_extract<T>())
481                         return *j;
482         return 0;
483 }
484
485 } // namespace DataFile
486 } // namespace Msp
487
488 #endif