]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Make Collection::contains check for 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 "loader.h"
8
9 namespace Msp {
10 namespace DataFile {
11
12 /**
13 Helper struct to determine whether a Loader has a Collection typedef.
14 */
15 template<typename T>
16 struct NeedsCollection
17 {
18         struct Yes { char c[2]; };
19         struct No { char c; };
20
21         template<typename U>
22         static Yes f(typename U::Collection *);
23         template<typename U>
24         static No f(...);
25
26         enum { value = (sizeof(f<T>(0))==sizeof(Yes)) };
27 };
28
29 class CollectionItemTypeBase;
30
31 template<typename T>
32 class CollectionItemType;
33
34 /**
35 A collection of objects that can be loaded from a datafile.  Each object is
36 identified by a name, which must be unique across the entire collection.
37
38 While this class can be instantiated by itself and used for storing objects,
39 loading requires that a subclass defines the supported types.  See the add_type
40 method for details.
41 */
42 class Collection
43 {
44 public:
45         /**
46         Loads objects into a Collection.  Automatically picks up keywords from
47         defined item types.
48         */
49         class Loader: public DataFile::Loader
50         {
51                 template<typename T> friend class CollectionItemType;
52
53         private:
54                 template<typename T, typename S, bool = NeedsCollection<typename T::Loader>::value>
55                 struct Add;
56
57                 Collection &coll;
58
59         public:
60                 Loader(Collection &);
61                 Collection &get_object() const { return coll; }
62         private:
63                 template<typename T, typename S, typename C>
64                 void coll_item(const std::string &n)
65                 {
66                         RefPtr<T> it = new T;
67                         load_sub(*it, dynamic_cast<C &>(coll));
68                         coll.add<S>(n, it.get());
69                         it.release();
70                 }
71
72                 template<typename T, typename S>
73                 void item(const std::string &n)
74                 {
75                         RefPtr<T> it = new T;
76                         load_sub(*it);
77                         coll.add<S>(n, it.get());
78                         it.release();
79                 }
80         };
81
82 private:
83         typedef std::map<std::string, Variant> ItemMap;
84         typedef std::list<CollectionItemTypeBase *> TypeList;
85
86         TypeList types;
87         ItemMap items;
88
89         Collection(const Collection &);
90         Collection &operator=(const Collection &);
91 public:
92         Collection() { }
93         virtual ~Collection();
94
95         /** Adds an object into the collection.  The name must not pre-exist.  The
96         collection takes ownership of the object. */
97         template<typename T>
98         void add(const std::string &name, T *item)
99         {
100                 if(!item)
101                         throw std::invalid_argument("Collection::add(item)");
102
103                 RefPtr<typename RemoveConst<T>::Type> ptr(item);
104                 try
105                 {
106                         insert_unique(items, name, ptr);
107                 }
108                 catch(...)
109                 {
110                         // Avoid deleting the object
111                         ptr.release();
112                         throw;
113                 }
114         }
115
116         /// Gets a typed object from the collection.
117         template<typename T>
118         T &get(const std::string &name) const
119         {
120                 typedef typename RemoveConst<T>::Type NCT;
121
122                 return *get_item(items, name).value<RefPtr<NCT> >();
123         }
124
125         /** Gets a typed object from the collection.  If the name is not found in
126         and a creator for the item type is defined, it is invoked. */
127         template<typename T>
128         T &get(const std::string &);
129
130         /// Returns a list of the names of objects of one type in the collection.
131         template<typename T>
132         std::list<std::string> get_names() const
133         {
134                 std::list<std::string> result;
135                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
136                         if(i->second.check_type<RefPtr<typename RemoveConst<T>::Type> >())
137                                 result.push_back(i->first);
138                 return result;
139         }
140
141         /// Returns a list of objects of one type in the collection.
142         template<typename T>
143         std::list<T *> get_list() const
144         {
145                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
146
147                 std::list<T *> result;
148                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
149                         if(i->second.check_type<RPNCT>())
150                                 result.push_back(i->second.value<RPNCT>().get());
151                 return result;
152         }
153
154         /// Checks whether a typed object exists in the collection.
155         template<typename T>
156         bool contains(const std::string &name) const
157         {
158                 ItemMap::const_iterator i = items.find(name);
159                 if(i==items.end())
160                         return false;
161
162                 return i->second.check_type<typename RemoveConst<T>::Type>();
163         }
164
165         /// Returns the name of an item in the collection.
166         template<typename T>
167         const std::string &get_name(T *d) const
168         {
169                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
170
171                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
172                         if(i->second.check_type<RPNCT>())
173                                 if(i->second.value<RPNCT>().get()==d)
174                                         return i->first;
175         
176                 // XXX Need better exception class
177                 throw std::runtime_error("Item not found in collection");
178         }
179
180 protected:
181         /** Adds a type to the collection.  The returned descriptor object reference
182         can be used to define how objects of that type can be loaded. */
183         template<typename T>
184         CollectionItemType<T> &add_type();
185 };
186
187
188 template<typename T, typename S>
189 struct Collection::Loader::Add<T, S, false>
190 {
191         static void add(Loader &loader, const std::string &kwd)
192         { loader.add(kwd, &Loader::item<T, S>); }
193 };
194
195 template<typename T, typename S>
196 struct Collection::Loader::Add<T, S, true>
197 {
198         static void add(Loader &loader, const std::string &kwd)
199         { loader.add(kwd, &Loader::coll_item<T, S, typename T::Loader::Collection>); }
200 };
201
202
203 class CollectionItemTypeBase
204 {
205 protected:
206         class TagBase
207         {
208         protected:
209                 TagBase() { }
210         public:
211                 virtual ~TagBase() { }
212         };
213
214         template<typename T>
215         class Tag: public TagBase
216         {
217         public:
218                 virtual ~Tag() { }
219         };
220
221         std::string kwd;
222         TagBase *tag;
223
224         CollectionItemTypeBase(): tag(0) { }
225 public:
226         virtual ~CollectionItemTypeBase()
227         { delete tag; }
228
229         virtual void add_to_loader(Collection::Loader &) const = 0;
230         virtual bool can_create() const = 0;
231         virtual void create_item(Collection &, const std::string &) const = 0;
232
233         template<typename T>
234         bool check_type() const
235         { return dynamic_cast<Tag<T> *>(tag); }
236 };
237
238
239 /**
240 Describes a type of item that can be loaded by a Collection.  These are created
241 by Collection::add_type.
242 */
243 template<typename T>
244 class CollectionItemType: public CollectionItemTypeBase
245 {
246 private:
247         class CreatorBase
248         {
249         protected:
250                 CreatorBase() { }
251         public:
252                 virtual ~CreatorBase() { }
253
254                 virtual T *create(Collection &, const std::string &) const = 0;
255         };
256
257         template<typename C>
258         class Creator: public CreatorBase
259         {
260         public:
261                 typedef T *(C::*FuncPtr)(const std::string &);
262
263         private:
264                 FuncPtr func;
265
266         public:
267                 Creator(FuncPtr f): func(f) { }
268
269                 virtual T *create(Collection &coll, const std::string &name) const
270                 { return (static_cast<C &>(coll).*func)(name); }
271         };
272
273         class StoreBase
274         {
275         protected:
276                 StoreBase() { }
277         public:
278                 virtual ~StoreBase() { }
279
280                 virtual void store(Collection &, const std::string &, T *) = 0;
281
282                 virtual void add_to_loader(Collection::Loader &, const std::string &) = 0;
283         };
284
285         template<typename S>
286         class Store: public StoreBase
287         {
288         public:
289                 virtual void store(Collection &coll, const std::string &name, T *obj)
290                 { coll.add(name, static_cast<S *>(obj)); }
291
292                 virtual void add_to_loader(Collection::Loader &loader, const std::string &kwd)
293                 { Collection::Loader::Add<T, S>::add(loader, kwd); }
294         };
295
296         CreatorBase *creat;
297         StoreBase *store;
298
299 public:
300         CollectionItemType():
301                 creat(0), store(new Store<T>)
302         { tag = new Tag<T>; }
303
304         ~CollectionItemType()
305         {
306                 delete creat;
307                 delete store;
308         }
309
310         /** Sets a datafile keyword for this item type.  The Collection's loader
311         will accept a statement with this keyword and a single string argument - the
312         item's name. */
313         CollectionItemType &keyword(const std::string &k)
314         {
315                 kwd = k;
316                 return *this;
317         }
318
319         /** Attaches a creator function to this item type.  If an item is not found
320         in the Collection, the creator function for its type is called to create it.
321         The function must be a member of the Collection subclass containing the
322         type.  It must return the created object, or null if it could not be
323         created.  It's also permissible to load the item via other means and then
324         return null. */
325         template<typename C>
326         CollectionItemType &creator(T *(C::*func)(const std::string &))
327         {
328                 delete creat;
329                 creat = new Creator<C>(func);
330                 return *this;
331         }
332
333         /** Specifies the storage type for items of this type.  It must be a base
334         class of the actual type.  */
335         template<typename S>
336         CollectionItemType &store_as()
337         {
338                 delete tag;
339                 tag = new Tag<S>;
340                 delete store;
341                 store = new Store<S>;
342                 return *this;
343         }
344
345         virtual void add_to_loader(Collection::Loader &loader) const
346         { store->add_to_loader(loader, kwd); }
347
348         virtual bool can_create() const
349         { return creat!=0; }
350
351         virtual void create_item(Collection &coll, const std::string &name) const
352         {
353                 if(!creat)
354                         throw std::runtime_error("no creator");
355                 T *obj = creat->create(coll, name);
356                 if(obj)
357                         store->store(coll, name, obj);
358         }
359 };
360
361
362 template<typename T>
363 T &Collection::get(const std::string &name)
364 {
365         typedef typename RemoveConst<T>::Type NCT;
366
367         if(!items.count(name))
368         {
369                 for(TypeList::iterator i=types.begin(); i!=types.end(); ++i)
370                         if((*i)->can_create() && (*i)->check_type<NCT>())
371                                 (*i)->create_item(*this, name);
372         }
373
374         return *get_item(items, name).value<RefPtr<NCT> >();
375 }
376
377 template<typename T>
378 CollectionItemType<T> &Collection::add_type()
379 {
380         CollectionItemType<T> *type = new CollectionItemType<T>;
381         types.push_back(type);
382         return *type;
383 }
384
385 } // namespace DataFile
386 } // namespace Msp
387
388 #endif