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