]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Turn internal type helpers into structs
[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                 return *var.value<RefPtr<typename RemoveConst<T>::Type> >();
131         }
132
133         template<typename T>
134         void collect_items(std::list<T *> *objects, std::list<std::string> *names, std::list<std::string> *future_names) const
135         {
136                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
137
138                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
139                         if(i->second.check_type<RPNCT>())
140                         {
141                                 if(objects)
142                                         objects->push_back(i->second.value<RPNCT>().get());
143                                 if(names)
144                                         names->push_back(i->first);
145                         }
146
147                 if(future_names)
148                         if(CollectionItemTypeBase *type = get_type<T>())
149                         {
150                                 for(SourceList::const_iterator i=sources.begin(); i!=sources.end(); ++i)
151                                 {
152                                         std::list<std::string> available_names = (*i)->get_names(*type);
153                                         for(std::list<std::string>::iterator j=available_names.begin(); j!=available_names.end(); ++j)
154                                                 if(!items.count(*j))
155                                                         future_names->push_back(*j);
156                                 }
157                         }
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> result;
166                 collect_items<T>(0, &result, 0);
167                 return result;
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> result;
176                 collect_items<T>(0, &result, &result);
177                 return result;
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<T *> result;
185                 collect_items<T>(&result, 0, 0);
186                 return result;
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                 std::list<T *> result;
195                 std::list<std::string> future;
196                 collect_items<T>(&result, 0, &future);
197                 for(std::list<std::string>::iterator i=future.begin(); i!=future.end(); ++i)
198                         result.push_back(&get<T>(*i));
199                 return result;
200         }
201
202 private:
203         template<typename T>
204         unsigned get_status(const std::string &name) const
205         {
206                 ItemMap::const_iterator i = items.find(name);
207                 if(i==items.end())
208                 {
209                         if(CollectionItemTypeBase *type = get_type<T>())
210                         {
211                                 for(SourceList::const_iterator j=sources.begin(); j!=sources.end(); ++j)
212                                         if((*j)->is_loadable(*type, name))
213                                                 return 2;
214                         }
215                         return 0;
216                 }
217
218                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
219                 if(!i->second.check_type<RPNCT>())
220                         return 0;
221
222                 return 1;
223         }
224
225 public:
226         /// Checks whether a typed object exists in the collection.
227         template<typename T>
228         bool contains(const std::string &name) const
229         { return get_status<T>(name)==1; }
230
231         /** Checks whether a typed object exists in the collection or is loadable
232         from a source. */
233         template<typename T>
234         bool contains(const std::string &name)
235         { return get_status<T>(name)>0; }
236
237         /// Returns the name of an item in the collection.
238         template<typename T>
239         const std::string &get_name(T *d) const
240         {
241                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
242
243                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
244                         if(i->second.check_type<RPNCT>())
245                                 if(i->second.value<RPNCT>().get()==d)
246                                         return i->first;
247         
248                 // XXX Need better exception class
249                 throw std::runtime_error("Item not found in collection");
250         }
251
252 protected:
253         /** Adds a type to the collection.  The returned descriptor object reference
254         can be used to define how objects of that type can be loaded. */
255         template<typename T>
256         CollectionItemType<T> &add_type();
257
258         /** Returns the descriptor for a type, or null if one isn't defined. */
259         template<typename T>
260         CollectionItemTypeBase *get_type() 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 TagBase
288         {
289                 virtual ~TagBase() { }
290         };
291
292         template<typename T>
293         struct Tag: TagBase
294         { };
295
296         std::string kwd;
297         std::vector<std::string> suffixes;
298         TagBase *tag;
299
300         CollectionItemTypeBase();
301 public:
302         virtual ~CollectionItemTypeBase();
303
304         void set_keyword(const std::string &);
305         const std::string &get_keyword() const { return kwd; }
306         void add_suffix(const std::string &);
307         bool match_name(const std::string &) const;
308         virtual void add_to_loader(Collection::Loader &) const = 0;
309         virtual bool can_create() const = 0;
310         virtual void create_item(Collection &, const std::string &) const = 0;
311         virtual void load_item(Collection &, Parser &, const std::string &) const = 0;
312
313         template<typename T>
314         bool check_type() const
315         { return dynamic_cast<Tag<T> *>(tag); }
316 };
317
318
319 /**
320 Describes a type of item that can be loaded by a Collection.  These are created
321 by Collection::add_type.
322 */
323 template<typename T>
324 class CollectionItemType: public CollectionItemTypeBase
325 {
326 private:
327         struct CreatorBase
328         {
329                 virtual ~CreatorBase() { }
330
331                 virtual T *create(Collection &, const std::string &) const = 0;
332         };
333
334         template<typename C>
335         struct Creator: CreatorBase
336         {
337                 typedef T *(C::*FuncPtr)(const std::string &);
338
339                 FuncPtr func;
340
341                 Creator(FuncPtr f): func(f) { }
342
343                 virtual T *create(Collection &coll, const std::string &name) const
344                 { return (static_cast<C &>(coll).*func)(name); }
345         };
346
347         struct StoreBase
348         {
349                 virtual ~StoreBase() { }
350
351                 virtual void store(Collection &, const std::string &, T *) = 0;
352
353                 virtual void add_to_loader(Collection::Loader &, const std::string &) = 0;
354         };
355
356         template<typename S>
357         struct Store: StoreBase
358         {
359                 virtual void store(Collection &coll, const std::string &name, T *obj)
360                 { coll.add(name, static_cast<S *>(obj)); }
361
362                 virtual void add_to_loader(Collection::Loader &loader, const std::string &kwd)
363                 { loader.add(kwd, &Collection::Loader::item<T, S>); }
364         };
365
366         CreatorBase *creat;
367         StoreBase *store;
368
369 public:
370         CollectionItemType():
371                 creat(0), store(new Store<T>)
372         { tag = new Tag<T>; }
373
374         ~CollectionItemType()
375         {
376                 delete creat;
377                 delete store;
378         }
379
380         /** Sets a datafile keyword for this item type.  The Collection's loader
381         will accept a statement with this keyword and a single string argument - the
382         item's name. */
383         CollectionItemType &keyword(const std::string &k)
384         {
385                 set_keyword(k);
386                 return *this;
387         }
388
389         /** Adds a suffix that is used to match names when looking for future
390         objects.  There is no implied separator; a name matches if it ends with the
391         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
392         added as a suffix. */
393         CollectionItemType &suffix(const std::string &s)
394         {
395                 add_suffix(s);
396                 return *this;
397         }
398
399         /** Attaches a creator function to this item type.  If an item is not found
400         in the Collection, the creator function for its type is called to create it.
401         The function must be a member of the Collection subclass containing the
402         type.  It must return the created object, or null if it could not be
403         created.  It's also permissible to load the item via other means and then
404         return null. */
405         template<typename C>
406         CollectionItemType &creator(T *(C::*func)(const std::string &))
407         {
408                 delete creat;
409                 creat = new Creator<C>(func);
410                 return *this;
411         }
412
413         /** Specifies the storage type for items of this type.  It must be a base
414         class of the actual type.  */
415         template<typename S>
416         CollectionItemType &store_as()
417         {
418                 delete tag;
419                 tag = new Tag<S>;
420                 delete store;
421                 store = new Store<S>;
422                 return *this;
423         }
424
425         virtual void add_to_loader(Collection::Loader &loader) const
426         { store->add_to_loader(loader, kwd); }
427
428         virtual bool can_create() const
429         { return creat!=0; }
430
431         virtual void create_item(Collection &coll, const std::string &name) const
432         {
433                 if(!creat)
434                         throw std::runtime_error("no creator");
435                 T *obj = creat->create(coll, name);
436                 if(obj)
437                         store->store(coll, name, obj);
438         }
439
440         virtual void load_item(Collection &coll, Parser &parser, const std::string &name) const
441         {
442                 RefPtr<T> obj = new T;
443                 Collection::ItemLoader<T> ldr(*obj, coll);
444                 ldr.load(parser);
445                 store->store(coll, name, obj.get());
446                 obj.release();
447         }
448 };
449
450
451 template<typename T>
452 CollectionItemType<T> &Collection::add_type()
453 {
454         CollectionItemType<T> *type = new CollectionItemType<T>;
455         types.push_back(type);
456         return *type;
457 }
458
459 template<typename T>
460 CollectionItemTypeBase *Collection::get_type() const
461 {
462         for(TypeList::const_iterator j=types.begin(); j!=types.end(); ++j)
463                 if((*j)->check_type<typename RemoveConst<T>::Type>())
464                         return *j;
465         return 0;
466 }
467
468 } // namespace DataFile
469 } // namespace Msp
470
471 #endif