]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Minor reorganization
[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 Collections also support a notion of "future objects".  These are objects which
43 are known to be possible to load, but loading them is deferred to the first
44 time they are requested.
45 */
46 class Collection
47 {
48 public:
49         /**
50         Loads objects into a Collection.  Automatically picks up keywords from
51         defined item types.
52         */
53         class Loader: public DataFile::Loader
54         {
55                 template<typename T> friend class CollectionItemType;
56
57         private:
58                 template<typename T, typename S, bool = NeedsCollection<typename T::Loader>::value>
59                 struct Add;
60
61                 Collection &coll;
62
63         public:
64                 Loader(Collection &);
65                 Collection &get_object() const { return coll; }
66         private:
67                 template<typename T, typename S, typename C>
68                 void coll_item(const std::string &n)
69                 {
70                         RefPtr<T> it = new T;
71                         load_sub(*it, dynamic_cast<C &>(coll));
72                         coll.add<S>(n, it.get());
73                         it.release();
74                 }
75
76                 template<typename T, typename S>
77                 void item(const std::string &n)
78                 {
79                         RefPtr<T> it = new T;
80                         load_sub(*it);
81                         coll.add<S>(n, it.get());
82                         it.release();
83                 }
84         };
85
86 private:
87         typedef std::map<std::string, Variant> ItemMap;
88         typedef std::list<CollectionItemTypeBase *> TypeList;
89
90         TypeList types;
91         ItemMap items;
92
93         Collection(const Collection &);
94         Collection &operator=(const Collection &);
95 public:
96         Collection() { }
97         virtual ~Collection();
98
99         /** Adds an object into the collection.  The name must not pre-exist.  The
100         collection takes ownership of the object. */
101         template<typename T>
102         void add(const std::string &name, T *item)
103         {
104                 if(!item)
105                         throw std::invalid_argument("Collection::add(item)");
106
107                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
108
109                 ItemMap::iterator i = items.find(name);
110                 if(i!=items.end())
111                 {
112                         if(i->second.check_type<RPNCT>())
113                         {
114                                 // Replace a future object placeholder
115                                 RPNCT &ptr = i->second.value<RPNCT>();
116                                 if(!ptr)
117                                 {
118                                         ptr = item;
119                                         return;
120                                 }
121                         }
122
123                         throw key_error(typeid(ItemMap));
124                 }
125
126                 items.insert(ItemMap::value_type(name, RPNCT(item)));
127         }
128
129 protected:
130         /** Adds the name of a future object to the collection.  The object itself
131         will be loaded on first access.  The calling subclass should be prepared to
132         create the object on request. */
133         template<typename T>
134         void add_future(const std::string &name)
135         {
136                 RefPtr<typename RemoveConst<T>::Type> ptr(0);
137                 insert_unique(items, name, ptr);
138         }
139
140 public:
141         /// Gets a typed object from the collection.
142         template<typename T>
143         T &get(const std::string &name) const
144         {
145                 typedef typename RemoveConst<T>::Type NCT;
146
147                 T *ptr = get_item(items, name).value<RefPtr<NCT> >();
148                 if(!ptr)
149                         throw key_error(typeid(ItemMap));
150                 return *ptr;
151         }
152
153         /** Gets a typed object from the collection.  If the name is not found in
154         and a creator for the item type is defined, it is invoked. */
155         template<typename T>
156         T &get(const std::string &);
157
158 private:
159         template<typename T>
160         void collect_items(std::list<T *> *objects, std::list<std::string> *names, std::list<std::string> *future_names)
161         {
162                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
163
164                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
165                         if(i->second.check_type<RPNCT>())
166                         {
167                                 T *ptr = i->second.value<RPNCT>().get();
168                                 if(ptr)
169                                 {
170                                         if(objects)
171                                                 objects->push_back(ptr);
172                                         if(names)
173                                                 names->push_back(i->first);
174                                 }
175                                 else if(future_names)
176                                         future_names->push_back(i->first);
177                         }
178         }
179
180 public:
181         /** Returns a list of the names of loaded objects of one type in the
182         collection. */
183         template<typename T>
184         std::list<std::string> get_names() const
185         {
186                 std::list<std::string> result;
187                 collect_items<T>(0, &result, 0);
188                 return result;
189         }
190
191         /** Returns a list of the names of objects of one type in the collection,
192         including any future objects. */
193         template<typename T>
194         std::list<std::string> get_names()
195         {
196                 std::list<std::string> result;
197                 collect_items<T>(0, &result, &result);
198                 return result;
199         }
200
201         /// Returns a list of loaded objects of one type in the collection.
202         template<typename T>
203         std::list<T *> get_list() const
204         {
205                 std::list<T *> result;
206                 collect_items<T>(&result, 0, 0);
207                 return result;
208         }
209
210         /** Returns a list of objects of one type in the collection.  Any future
211         objects of that type are loaded and returned in the list. */
212         template<typename T>
213         std::list<T *> get_list()
214         {
215                 std::list<T *> result;
216                 std::list<std::string> future;
217                 collect_items<T>(&result, 0, &future);
218                 for(std::list<std::string>::iterator i=future.begin(); i!=future.end(); ++i)
219                         result.push_back(&get<T>(*i));
220                 return result;
221         }
222
223 private:
224         template<typename T>
225         unsigned get_status(const std::string &name) const
226         {
227                 ItemMap::const_iterator i = items.find(name);
228                 if(i==items.end())
229                         return false;
230
231                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
232                 if(!i->second.check_type<RPNCT>())
233                         return false;
234
235                 T *ptr = i->second.value<RPNCT>().get();
236                 return ptr ? 1 : 2;
237         }
238
239 public:
240         /// Checks whether a typed object exists and is loaded in the collection.
241         template<typename T>
242         bool contains(const std::string &name) const
243         { return get_status<T>(name)==1; }
244
245         /** Checks whether a typed object exists in the collection, as either a
246         loaded or future object. */
247         template<typename T>
248         bool contains(const std::string &name)
249         { return get_status<T>(name)>0; }
250
251         /// Returns the name of an item in the collection.
252         template<typename T>
253         const std::string &get_name(T *d) const
254         {
255                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
256
257                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
258                         if(i->second.check_type<RPNCT>())
259                                 if(i->second.value<RPNCT>().get()==d)
260                                         return i->first;
261         
262                 // XXX Need better exception class
263                 throw std::runtime_error("Item not found in collection");
264         }
265
266 protected:
267         /** Adds a type to the collection.  The returned descriptor object reference
268         can be used to define how objects of that type can be loaded. */
269         template<typename T>
270         CollectionItemType<T> &add_type();
271 };
272
273
274 template<typename T, typename S>
275 struct Collection::Loader::Add<T, S, false>
276 {
277         static void add(Loader &loader, const std::string &kwd)
278         { loader.add(kwd, &Loader::item<T, S>); }
279 };
280
281 template<typename T, typename S>
282 struct Collection::Loader::Add<T, S, true>
283 {
284         static void add(Loader &loader, const std::string &kwd)
285         { loader.add(kwd, &Loader::coll_item<T, S, typename T::Loader::Collection>); }
286 };
287
288
289 class CollectionItemTypeBase
290 {
291 protected:
292         class TagBase
293         {
294         protected:
295                 TagBase() { }
296         public:
297                 virtual ~TagBase() { }
298         };
299
300         template<typename T>
301         class Tag: public TagBase
302         { };
303
304         std::string kwd;
305         TagBase *tag;
306
307         CollectionItemTypeBase();
308 public:
309         virtual ~CollectionItemTypeBase();
310
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
315         template<typename T>
316         bool check_type() const
317         { return dynamic_cast<Tag<T> *>(tag); }
318 };
319
320
321 /**
322 Describes a type of item that can be loaded by a Collection.  These are created
323 by Collection::add_type.
324 */
325 template<typename T>
326 class CollectionItemType: public CollectionItemTypeBase
327 {
328 private:
329         class CreatorBase
330         {
331         protected:
332                 CreatorBase() { }
333         public:
334                 virtual ~CreatorBase() { }
335
336                 virtual T *create(Collection &, const std::string &) const = 0;
337         };
338
339         template<typename C>
340         class Creator: public CreatorBase
341         {
342         public:
343                 typedef T *(C::*FuncPtr)(const std::string &);
344
345         private:
346                 FuncPtr func;
347
348         public:
349                 Creator(FuncPtr f): func(f) { }
350
351                 virtual T *create(Collection &coll, const std::string &name) const
352                 { return (static_cast<C &>(coll).*func)(name); }
353         };
354
355         class StoreBase
356         {
357         protected:
358                 StoreBase() { }
359         public:
360                 virtual ~StoreBase() { }
361
362                 virtual void store(Collection &, const std::string &, T *) = 0;
363
364                 virtual void add_to_loader(Collection::Loader &, const std::string &) = 0;
365         };
366
367         template<typename S>
368         class Store: public StoreBase
369         {
370         public:
371                 virtual void store(Collection &coll, const std::string &name, T *obj)
372                 { coll.add(name, static_cast<S *>(obj)); }
373
374                 virtual void add_to_loader(Collection::Loader &loader, const std::string &kwd)
375                 { Collection::Loader::Add<T, S>::add(loader, kwd); }
376         };
377
378         CreatorBase *creat;
379         StoreBase *store;
380
381 public:
382         CollectionItemType():
383                 creat(0), store(new Store<T>)
384         { tag = new Tag<T>; }
385
386         ~CollectionItemType()
387         {
388                 delete creat;
389                 delete store;
390         }
391
392         /** Sets a datafile keyword for this item type.  The Collection's loader
393         will accept a statement with this keyword and a single string argument - the
394         item's name. */
395         CollectionItemType &keyword(const std::string &k)
396         {
397                 kwd = k;
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         /** Specifies the storage type for items of this type.  It must be a base
416         class of the actual type.  */
417         template<typename S>
418         CollectionItemType &store_as()
419         {
420                 delete tag;
421                 tag = new Tag<S>;
422                 delete store;
423                 store = new Store<S>;
424                 return *this;
425         }
426
427         virtual void add_to_loader(Collection::Loader &loader) const
428         { store->add_to_loader(loader, kwd); }
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                         store->store(coll, name, obj);
440         }
441 };
442
443
444 template<typename T>
445 T &Collection::get(const std::string &name)
446 {
447         typedef typename RemoveConst<T>::Type NCT;
448
449         ItemMap::iterator i = items.find(name);
450         if(i!=items.end())
451         {
452                 NCT *ptr = i->second.value<RefPtr<NCT> >().get();
453                 if(ptr)
454                         return *ptr;
455         }
456
457         for(TypeList::iterator j=types.begin(); j!=types.end(); ++j)
458                 if((*j)->can_create() && (*j)->check_type<NCT>())
459                         (*j)->create_item(*this, name);
460
461         return *get_item(items, name).value<RefPtr<NCT> >();
462 }
463
464 template<typename T>
465 CollectionItemType<T> &Collection::add_type()
466 {
467         CollectionItemType<T> *type = new CollectionItemType<T>;
468         types.push_back(type);
469         return *type;
470 }
471
472 } // namespace DataFile
473 } // namespace Msp
474
475 #endif