]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Add an intelligent packed collection class
[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 Other classes are available to provide refined ways of loading objects from
47 files.  See DirectoryCollection and PackCollection.
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
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                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
104
105                 ItemMap::iterator i = items.find(name);
106                 if(i!=items.end())
107                 {
108                         if(i->second.check_type<RPNCT>())
109                         {
110                                 // Replace a future object placeholder
111                                 RPNCT &ptr = i->second.value<RPNCT>();
112                                 if(!ptr)
113                                 {
114                                         ptr = item;
115                                         return;
116                                 }
117                         }
118
119                         throw key_error(typeid(ItemMap));
120                 }
121
122                 items.insert(ItemMap::value_type(name, RPNCT(item)));
123         }
124
125 protected:
126         /** Adds the name of a future object to the collection.  The object itself
127         will be loaded on first access.  The calling subclass should be prepared to
128         create the object on request. */
129         template<typename T>
130         void add_future(const std::string &name)
131         {
132                 RefPtr<typename RemoveConst<T>::Type> ptr(0);
133                 insert_unique(items, name, ptr);
134         }
135
136         /** Adds the name of a future object, guessing its type.  If a type matching
137         the name can't be found, nothing is done. */
138         void add_future(const std::string &name);
139
140         /** Adds the name of a future object, using a keyword to determine its type.
141         The keyword must be known to the collection. */
142         void add_future_with_keyword(const std::string &name, const std::string &);
143
144 public:
145         /// Gets a typed object from the collection.
146         template<typename T>
147         T &get(const std::string &name) const
148         {
149                 typedef typename RemoveConst<T>::Type NCT;
150
151                 T *ptr = get_item(items, name).value<RefPtr<NCT> >().get();
152                 if(!ptr)
153                         throw key_error(typeid(ItemMap));
154                 return *ptr;
155         }
156
157         /** Gets a typed object from the collection.  If the name is not found in
158         and a creator for the item type is defined, it is invoked. */
159         template<typename T>
160         T &get(const std::string &);
161
162 private:
163         template<typename T>
164         void collect_items(std::list<T *> *objects, std::list<std::string> *names, std::list<std::string> *future_names) const
165         {
166                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
167
168                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
169                         if(i->second.check_type<RPNCT>())
170                         {
171                                 T *ptr = i->second.value<RPNCT>().get();
172                                 if(ptr)
173                                 {
174                                         if(objects)
175                                                 objects->push_back(ptr);
176                                         if(names)
177                                                 names->push_back(i->first);
178                                 }
179                                 else if(future_names)
180                                         future_names->push_back(i->first);
181                         }
182         }
183
184 public:
185         /** Returns a list of the names of loaded objects of one type in the
186         collection. */
187         template<typename T>
188         std::list<std::string> get_names() const
189         {
190                 std::list<std::string> result;
191                 collect_items<T>(0, &result, 0);
192                 return result;
193         }
194
195         /** Returns a list of the names of objects of one type in the collection,
196         including any future objects. */
197         template<typename T>
198         std::list<std::string> get_names()
199         {
200                 std::list<std::string> result;
201                 collect_items<T>(0, &result, &result);
202                 return result;
203         }
204
205         /// Returns a list of loaded objects of one type in the collection.
206         template<typename T>
207         std::list<T *> get_list() const
208         {
209                 std::list<T *> result;
210                 collect_items<T>(&result, 0, 0);
211                 return result;
212         }
213
214         /** Returns a list of objects of one type in the collection.  Any future
215         objects of that type are loaded and returned in the list. */
216         template<typename T>
217         std::list<T *> get_list()
218         {
219                 std::list<T *> result;
220                 std::list<std::string> future;
221                 collect_items<T>(&result, 0, &future);
222                 for(std::list<std::string>::iterator i=future.begin(); i!=future.end(); ++i)
223                         result.push_back(&get<T>(*i));
224                 return result;
225         }
226
227 private:
228         template<typename T>
229         unsigned get_status(const std::string &name) const
230         {
231                 ItemMap::const_iterator i = items.find(name);
232                 if(i==items.end())
233                         return false;
234
235                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
236                 if(!i->second.check_type<RPNCT>())
237                         return false;
238
239                 T *ptr = i->second.value<RPNCT>().get();
240                 return ptr ? 1 : 2;
241         }
242
243 public:
244         /// Checks whether a typed object exists and is loaded in the collection.
245         template<typename T>
246         bool contains(const std::string &name) const
247         { return get_status<T>(name)==1; }
248
249         /** Checks whether a typed object exists in the collection, as either a
250         loaded or future object. */
251         template<typename T>
252         bool contains(const std::string &name)
253         { return get_status<T>(name)>0; }
254
255         /// Returns the name of an item in the collection.
256         template<typename T>
257         const std::string &get_name(T *d) const
258         {
259                 typedef RefPtr<typename RemoveConst<T>::Type> RPNCT;
260
261                 for(ItemMap::const_iterator i=items.begin(); i!=items.end(); ++i)
262                         if(i->second.check_type<RPNCT>())
263                                 if(i->second.value<RPNCT>().get()==d)
264                                         return i->first;
265         
266                 // XXX Need better exception class
267                 throw std::runtime_error("Item not found in collection");
268         }
269
270 protected:
271         /** Adds a type to the collection.  The returned descriptor object reference
272         can be used to define how objects of that type can be loaded. */
273         template<typename T>
274         CollectionItemType<T> &add_type();
275 };
276
277 template<typename T>
278 class Collection::ItemLoader<T, false>: public T::Loader
279 {
280 public:
281         ItemLoader(T &o, Collection &):
282                 T::Loader(o)
283         { }
284 };
285
286 template<typename T>
287 class Collection::ItemLoader<T, true>: public T::Loader
288 {
289 public:
290         ItemLoader(T &o, Collection &c):
291                 T::Loader(o, dynamic_cast<typename T::Loader::Collection &>(c))
292         { }
293 };
294
295
296 class CollectionItemTypeBase
297 {
298 protected:
299         class TagBase
300         {
301         protected:
302                 TagBase() { }
303         public:
304                 virtual ~TagBase() { }
305         };
306
307         template<typename T>
308         class Tag: public TagBase
309         { };
310
311         std::string kwd;
312         std::vector<std::string> suffixes;
313         TagBase *tag;
314
315         CollectionItemTypeBase();
316 public:
317         virtual ~CollectionItemTypeBase();
318
319         void set_keyword(const std::string &);
320         const std::string &get_keyword() const { return kwd; }
321         void add_suffix(const std::string &);
322         bool match_name(const std::string &) const;
323         virtual void add_to_loader(Collection::Loader &) const = 0;
324         virtual bool can_create() const = 0;
325         virtual void create_item(Collection &, const std::string &) const = 0;
326         virtual Variant create_future() const = 0;
327
328         template<typename T>
329         bool check_type() const
330         { return dynamic_cast<Tag<T> *>(tag); }
331 };
332
333
334 /**
335 Describes a type of item that can be loaded by a Collection.  These are created
336 by Collection::add_type.
337 */
338 template<typename T>
339 class CollectionItemType: public CollectionItemTypeBase
340 {
341 private:
342         class CreatorBase
343         {
344         protected:
345                 CreatorBase() { }
346         public:
347                 virtual ~CreatorBase() { }
348
349                 virtual T *create(Collection &, const std::string &) const = 0;
350         };
351
352         template<typename C>
353         class Creator: public CreatorBase
354         {
355         public:
356                 typedef T *(C::*FuncPtr)(const std::string &);
357
358         private:
359                 FuncPtr func;
360
361         public:
362                 Creator(FuncPtr f): func(f) { }
363
364                 virtual T *create(Collection &coll, const std::string &name) const
365                 { return (static_cast<C &>(coll).*func)(name); }
366         };
367
368         class StoreBase
369         {
370         protected:
371                 StoreBase() { }
372         public:
373                 virtual ~StoreBase() { }
374
375                 virtual void store(Collection &, const std::string &, T *) = 0;
376                 virtual Variant create_future() const = 0;
377
378                 virtual void add_to_loader(Collection::Loader &, const std::string &) = 0;
379         };
380
381         template<typename S>
382         class Store: public StoreBase
383         {
384         public:
385                 virtual void store(Collection &coll, const std::string &name, T *obj)
386                 { coll.add(name, static_cast<S *>(obj)); }
387
388                 virtual Variant create_future() const
389                 { return RefPtr<S>(0); }
390
391                 virtual void add_to_loader(Collection::Loader &loader, const std::string &kwd)
392                 { loader.add(kwd, &Collection::Loader::item<T, S>); }
393         };
394
395         CreatorBase *creat;
396         StoreBase *store;
397
398 public:
399         CollectionItemType():
400                 creat(0), store(new Store<T>)
401         { tag = new Tag<T>; }
402
403         ~CollectionItemType()
404         {
405                 delete creat;
406                 delete store;
407         }
408
409         /** Sets a datafile keyword for this item type.  The Collection's loader
410         will accept a statement with this keyword and a single string argument - the
411         item's name. */
412         CollectionItemType &keyword(const std::string &k)
413         {
414                 set_keyword(k);
415                 return *this;
416         }
417
418         /** Adds a suffix that is used to match names when looking for future
419         objects.  There is no implied separator; a name matches if it ends with the
420         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
421         added as a suffix. */
422         CollectionItemType &suffix(const std::string &s)
423         {
424                 add_suffix(s);
425                 return *this;
426         }
427
428         /** Attaches a creator function to this item type.  If an item is not found
429         in the Collection, the creator function for its type is called to create it.
430         The function must be a member of the Collection subclass containing the
431         type.  It must return the created object, or null if it could not be
432         created.  It's also permissible to load the item via other means and then
433         return null. */
434         template<typename C>
435         CollectionItemType &creator(T *(C::*func)(const std::string &))
436         {
437                 delete creat;
438                 creat = new Creator<C>(func);
439                 return *this;
440         }
441
442         /** Specifies the storage type for items of this type.  It must be a base
443         class of the actual type.  */
444         template<typename S>
445         CollectionItemType &store_as()
446         {
447                 delete tag;
448                 tag = new Tag<S>;
449                 delete store;
450                 store = new Store<S>;
451                 return *this;
452         }
453
454         virtual void add_to_loader(Collection::Loader &loader) const
455         { store->add_to_loader(loader, kwd); }
456
457         virtual bool can_create() const
458         { return creat!=0; }
459
460         virtual void create_item(Collection &coll, const std::string &name) const
461         {
462                 if(!creat)
463                         throw std::runtime_error("no creator");
464                 T *obj = creat->create(coll, name);
465                 if(obj)
466                         store->store(coll, name, obj);
467         }
468
469         virtual Variant create_future() const
470         { return store->create_future(); }
471 };
472
473
474 template<typename T>
475 T &Collection::get(const std::string &name)
476 {
477         typedef typename RemoveConst<T>::Type NCT;
478
479         ItemMap::iterator i = items.find(name);
480         if(i!=items.end())
481         {
482                 NCT *ptr = i->second.value<RefPtr<NCT> >().get();
483                 if(ptr)
484                         return *ptr;
485         }
486
487         for(TypeList::iterator j=types.begin(); j!=types.end(); ++j)
488                 if((*j)->can_create() && (*j)->check_type<NCT>())
489                         (*j)->create_item(*this, name);
490
491         NCT *ptr = get_item(items, name).value<RefPtr<NCT> >().get();
492         if(!ptr)
493                 throw key_error(typeid(ItemMap));
494         return *ptr;
495 }
496
497 template<typename T>
498 CollectionItemType<T> &Collection::add_type()
499 {
500         CollectionItemType<T> *type = new CollectionItemType<T>;
501         types.push_back(type);
502         return *type;
503 }
504
505 } // namespace DataFile
506 } // namespace Msp
507
508 #endif