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