]> git.tdb.fi Git - libs/datafile.git/blob - source/collection.h
Cosmetic changes
[libs/datafile.git] / source / collection.h
1 #ifndef MSP_DATAFILE_COLLECTION_H_
2 #define MSP_DATAFILE_COLLECTION_H_
3
4 #include <type_traits>
5 #include <msp/core/attributes.h>
6 #include <msp/core/maputils.h>
7 #include <msp/core/noncopyable.h>
8 #include <msp/core/refptr.h>
9 #include "collectionsource.h"
10 #include "loader.h"
11 #include "meta.h"
12
13 /* XXX This file is a big mess with too many things in it.  However, the
14 dependencies between those things make it difficult to split up. */
15
16 namespace Msp {
17 namespace DataFile {
18
19 class CollectionItemTypeBase;
20
21 template<typename T>
22 class CollectionItemType;
23
24 template<typename T>
25 class LoadableCollectionItemType;
26
27 template<typename T, bool = HasLoader<T>::value>
28 struct CollectionItemTypeChooser;
29
30 template<typename T>
31 struct CollectionItemTypeChooser<T, true>
32 { typedef LoadableCollectionItemType<T> Type; };
33
34 template<typename T>
35 struct CollectionItemTypeChooser<T, false>
36 { typedef CollectionItemType<T> Type; };
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 As a last resort, a fallback collection can be designated for loading items
50 that are not present.  Items retrieted from the fallback collection are shared
51 between the collections, and are only deleted when all collections in the chain
52 have been destroyed.
53 */
54 class Collection: private NonCopyable
55 {
56 public:
57         /**
58         Loads objects into a Collection.  Automatically picks up keywords from
59         defined item types.
60         */
61         class Loader: public DataFile::Loader
62         {
63                 template<typename T> friend class LoadableCollectionItemType;
64
65         private:
66                 Collection &coll;
67
68         public:
69                 Loader(Collection &);
70                 Collection &get_object() const { return coll; }
71         private:
72                 template<typename T, typename S>
73                 void item(const std::string &n)
74                 {
75                         RefPtr<T> it = new T;
76                         ItemLoader<T> ldr(*it, coll);
77                         load_sub_with(ldr);
78                         coll.add<S>(n, it.get());
79                         it.release();
80                 }
81         };
82
83         template<typename T, bool = NeedsCollection<typename T::Loader>::value>
84         class ItemLoader;
85
86 private:
87         typedef std::map<std::string, Variant> ItemMap;
88
89         std::vector<CollectionItemTypeBase *> types;
90         ItemMap items;
91         std::vector<const CollectionSource *> sources;
92         Collection *fallback = nullptr;
93
94 public:
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                 typedef typename std::remove_cv<T>::type NCT;
106                 RefPtr<NCT> ptr(item);
107                 try
108                 {
109                         add_var(name, get_type<NCT>(name), ptr);
110                 }
111                 catch(...)
112                 {
113                         // Avoid deleting the object
114                         ptr.release();
115                         throw;
116                 }
117         }
118
119         /// Gets a typed object from the collection.
120         template<typename T>
121         T &get(const std::string &name) const
122         {
123                 return extract<typename std::remove_cv<T>::type>(get_item(items, name));
124         }
125
126         /** Gets a typed object from the collection.  If the name is not found,
127         automatic creation with the type's creator function (if defined) or from
128         sources (if present) is attempted. */
129         template<typename T>
130         T &get(const std::string &name)
131         {
132                 typedef typename std::remove_cv<T>::type NCT;
133                 return extract<NCT>(get_var(name, get_type<NCT>(name)));
134         }
135
136         /** Finds a typed object in the collection.  Returns null if the name does
137         not exist.  Throws if the name exists but the object is of an incorrect
138         type. */
139         template<typename T>
140         T *find(const std::string &name) const
141         {
142                 ItemMap::const_iterator i = items.find(name);
143                 return (i!=items.end() ? extract<typename std::remove_cv<T>::type>(i->second) : 0);
144         }
145
146         template<typename T>
147         T *find(const std::string &name)
148         {
149                 typedef typename std::remove_cv<T>::type NCT;
150                 const Variant *var = find_var(name, get_type<NCT>(name));
151                 return (var ? &extract<NCT>(*var) : 0);
152         }
153
154 private:
155         void add_var(const std::string &, const CollectionItemTypeBase *, const Variant &);
156         const Variant &get_var(const std::string &, const CollectionItemTypeBase *);
157         const Variant *find_var(const std::string &, const CollectionItemTypeBase *);
158
159         template<typename T>
160         T &extract(const Variant &var) const;
161
162         template<typename T>
163         std::list<T *> extract_list(const std::vector<const Variant *> &vars) const
164         {
165                 std::list<T *> result;
166                 for(const Variant *v: vars)
167                         result.push_back(&extract<T>(*v));
168                 return result;
169         }
170
171         void gather_items(std::vector<const Variant *> *, std::list<std::string> *, const CollectionItemTypeBase &, bool) const;
172
173         template<typename T>
174         void gather_items(std::vector<const Variant *> *vars, std::list<std::string> *names, const CollectionItemTypeBase *type, bool include_sources) const
175         {
176                 if(type || (type = get_type<T>()))
177                         gather_items(vars, names, *type, include_sources);
178                 else
179                         gather_items(vars, names, CollectionItemType<T>(), false);
180         }
181
182 public:
183         /// Returns a list of the names of objects of one type in the collection.
184         template<typename T>
185         std::list<std::string> get_names() const
186         {
187                 std::list<std::string> names;
188                 gather_items<typename std::remove_cv<T>::type>(0, &names, 0, false);
189                 return names;
190         }
191
192         /** Returns a list of the names of objects of one type in the collection or
193         available from sources. */
194         template<typename T>
195         std::list<std::string> get_names()
196         {
197                 std::list<std::string> names;
198                 gather_items<typename std::remove_cv<T>::type>(0, &names, 0, true);
199                 return names;
200         }
201
202         /// Returns a list of objects of one type in the collection.
203         template<typename T>
204         std::list<T *> get_list() const
205         {
206                 std::vector<const Variant *> vars;
207                 gather_items<typename std::remove_cv<T>::type>(&vars, 0, 0, false);
208                 return extract_list<T>(vars);
209         }
210
211         /** Returns a list of objects of one type, loading them from sources if
212         necessary. */
213         template<typename T>
214         std::list<T *> get_list()
215         {
216                 CollectionItemTypeBase *type = get_type<typename std::remove_cv<T>::type>();
217                 if(type)
218                         load_items_from_sources(*type);
219
220                 std::vector<const Variant *> vars;
221                 gather_items<typename std::remove_cv<T>::type>(&vars, 0, type, true);
222                 return extract_list<T>(vars);
223         }
224
225 private:
226         unsigned get_status(const std::string &, const CollectionItemTypeBase &) const;
227
228         template<typename T>
229         unsigned get_status(const std::string &name) const
230         {
231                 // XXX Should go through all applicable types
232                 if(CollectionItemTypeBase *type = get_type<T>())
233                         return get_status(name, *type);
234
235                 ItemMap::const_iterator i = items.find(name);
236                 return (i!=items.end() && i->second.check_type<RefPtr<T> >());
237         }
238
239 public:
240         /// Checks whether a typed object exists in the collection.
241         template<typename T>
242         bool contains(const std::string &name) const
243         { return get_status<typename std::remove_cv<T>::type>(name)==1; }
244
245         /** Checks whether a typed object exists in the collection or is loadable
246         from a source. */
247         template<typename T>
248         bool contains(const std::string &name)
249         { return get_status<typename std::remove_cv<T>::type>(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 std::remove_cv<T>::type> RPNCT;
256
257                 for(const auto &kvp: items)
258                         if(kvp.second.check_type<RPNCT>())
259                                 if(kvp.second.value<RPNCT>().get()==d)
260                                         return kvp.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         typename CollectionItemTypeChooser<T>::Type &add_type();
271
272         /** Returns a mutable reference to an existing type descriptor.  This can be
273         used to e.g. override the creator function of a type added by a base class. */
274         template<typename T>
275         typename CollectionItemTypeChooser<T>::Type &modify_type();
276
277 private:
278         /** Returns the descriptor for a type, or null if one isn't defined.  An
279         optional name can be given to prioritize matching types. */
280         template<typename T>
281         CollectionItemTypeBase *get_type(const std::string & = std::string()) const;
282
283         /// Gets a descriptor with the same type as another descriptor.
284         CollectionItemTypeBase *get_type(const CollectionItemTypeBase &) const;
285
286         /// Returns the descriptor for an item, or null if it's of an unknown type.
287         CollectionItemTypeBase *get_type_for_item(const Variant &) const;
288
289 protected:
290         /** Adds a source for automatically loading items.  Sources are consulted
291         in the order they are added. */
292         void add_source(const CollectionSource &);
293
294 public:
295         /** Opens a raw resource, without interpreting it as object data.  Null is
296         returned if no such file is found.  The caller must dispose of the returned
297         object when done with it. */
298         IO::Seekable *open_raw(const std::string &) const;
299
300 private:
301         void gather_names_from_sources(std::list<std::string> &, const CollectionItemTypeBase &) const;
302
303         void load_items_from_sources(const CollectionItemTypeBase &);
304
305 protected:
306         /** Sets a fallback collection, which will be consulted if an item is not
307         found. */
308         void set_fallback(Collection *);
309
310         Collection *get_fallback() const { return fallback; }
311 };
312
313 template<typename T>
314 class Collection::ItemLoader<T, false>: public T::Loader
315 {
316 public:
317         ItemLoader(T &o, Collection &):
318                 T::Loader(o)
319         { }
320 };
321
322 template<typename T>
323 class Collection::ItemLoader<T, true>: public T::Loader
324 {
325 public:
326         ItemLoader(T &o, Collection &c):
327                 T::Loader(o, dynamic_cast<typename T::Loader::Collection &>(c))
328         { }
329 };
330
331
332 class CollectionItemTypeBase
333 {
334 protected:
335         struct ExtractorBase
336         {
337                 virtual ~ExtractorBase() = default;
338         };
339
340         template<typename T>
341         struct Extractor: ExtractorBase
342         {
343                 virtual T &extract(const Variant &) const = 0;
344         };
345
346         std::string kwd;
347         std::vector<std::string> suffixes;
348         std::vector<ExtractorBase *> extractors;
349
350         CollectionItemTypeBase() = default;
351 public:
352         virtual ~CollectionItemTypeBase();
353
354 protected:
355         void set_keyword(const std::string &);
356         void add_suffix(const std::string &);
357 public:
358         const std::string &get_keyword() const { return kwd; }
359         bool match_name(const std::string &) const;
360         virtual bool is_same_type(const CollectionItemTypeBase &) const = 0;
361         virtual bool check_item_type(const Variant &) const = 0;
362         virtual void add_to_loader(Collection::Loader &) const = 0;
363         virtual bool can_create() const = 0;
364         virtual void create_item(Collection &, const std::string &) const = 0;
365         virtual void load_item(Collection &, Parser &, const std::string &) const = 0;
366         virtual void notify_item(const std::string &, const Variant &) const = 0;
367
368         template<typename T>
369         bool can_extract() const
370         {
371                 for(ExtractorBase *e: extractors)
372                         if(dynamic_cast<Extractor<T> *>(e))
373                                 return true;
374                 return false;
375         }
376
377         template<typename T>
378         T *extract(const Variant &var) const
379         {
380                 for(ExtractorBase *e: extractors)
381                         if(Extractor<T> *ex = dynamic_cast<Extractor<T> *>(e))
382                                 return &ex->extract(var);
383                 return 0;
384         }
385 };
386
387
388 /**
389 Describes a type of item that can be loaded by a Collection.  These are created
390 by Collection::add_type.
391 */
392 template<typename T>
393 class CollectionItemType: public CollectionItemTypeBase
394 {
395 private:
396         template<typename B>
397         struct Extractor: CollectionItemTypeBase::Extractor<B>
398         {
399                 B &extract(const Variant &var) const override
400                 { return *var.value<RefPtr<T> >(); }
401         };
402
403         std::function<T *(const std::string &)> create_func;
404         std::vector<std::function<void(const std::string &, T &)>> notify_funcs;
405
406 public:
407         /** Sets a datafile keyword for this item type.  The Collection's loader
408         will accept a statement with this keyword and a single string argument - the
409         item's name. */
410         CollectionItemType &keyword(const std::string &k)
411         {
412                 set_keyword(k);
413                 return *this;
414         }
415
416         /** Adds a suffix that is used to match names when looking for loadable
417         objects.  There is no implied separator; a name matches if it ends with the
418         suffix.  If a keyword is defined before any suffixes, then "."+keyword is
419         added as a suffix. */
420         CollectionItemType &suffix(const std::string &s)
421         {
422                 add_suffix(s);
423                 return *this;
424         }
425
426         /** Attaches a creator function to this item type.  If an item is not found
427         in the Collection, the creator function for its type is called to create it.
428         The function must be a member of the Collection subclass containing the
429         type.  It must return the created object, or null if it could not be
430         created.  It's also permissible to load the item via other means and then
431         return null. */
432         template<typename F>
433         CollectionItemType &creator(F func)
434         {
435                 create_func = func;
436                 return *this;
437         }
438
439         /** Makes items of this type available through a base class. */
440         template<typename B>
441         CollectionItemType &base()
442         {
443                 extractors.push_back(new Extractor<B>);
444                 return *this;
445         }
446
447         template<typename F>
448         CollectionItemType &notify(F func)
449         {
450                 notify_funcs.emplace_back(func);
451                 return *this;
452         }
453
454         bool is_same_type(const CollectionItemTypeBase &other) const override
455         { return dynamic_cast<const CollectionItemType<T> *>(&other); }
456
457         bool check_item_type(const Variant &var) const override
458         { return var.check_type<RefPtr<T> >(); }
459
460         void add_to_loader(Collection::Loader &) const override
461         { }
462
463         bool can_create() const override
464         { return static_cast<bool>(create_func); }
465
466         void create_item(Collection &coll, const std::string &name) const override
467         {
468                 if(!create_func)
469                         throw std::runtime_error("no creator");
470                 T *obj = create_func(name);
471                 if(obj)
472                         coll.add(name, obj);
473         }
474
475         void load_item(Collection &, Parser &, const std::string &) const override
476         {
477                 throw std::runtime_error("this type cannot be loaded");
478         }
479
480         void notify_item(const std::string &name, const Variant &var) const override
481         {
482                 RefPtr<T> obj = var.value<RefPtr<T> >();
483                 for(const auto &n: notify_funcs)
484                         n(name, *obj);
485         }
486 };
487
488
489 template<typename T>
490 class LoadableCollectionItemType: public CollectionItemType<T>
491 {
492 public:
493         void add_to_loader(Collection::Loader &loader) const override
494         { loader.add(this->kwd, &Collection::Loader::item<T, T>); }
495
496         void load_item(Collection &coll, Parser &parser, const std::string &name) const override
497         {
498                 RefPtr<T> obj = new T;
499                 Collection::ItemLoader<T> ldr(*obj, coll);
500                 ldr.load(parser);
501                 coll.add(name, obj.get());
502                 obj.release();
503         }
504 };
505
506
507 template<typename T>
508 T &Collection::extract(const Variant &var) const
509 {
510         if(!var.check_type<RefPtr<T> >())
511                 if(CollectionItemTypeBase *type = get_type_for_item(var))
512                         if(T *item = type->extract<T>(var))
513                                 return *item;
514
515         return *var.value<RefPtr<T> >();
516 }
517
518 template<typename T>
519 typename CollectionItemTypeChooser<T>::Type &Collection::add_type()
520 {
521         typename CollectionItemTypeChooser<T>::Type *type = new typename CollectionItemTypeChooser<T>::Type;
522         types.push_back(type);
523         return *type;
524 }
525
526 template<typename T>
527 typename CollectionItemTypeChooser<T>::Type &Collection::modify_type()
528 {
529         for(CollectionItemTypeBase *t: types)
530                 if(CollectionItemType<T> *tt = dynamic_cast<CollectionItemType<T> *>(t))
531                         return *tt;
532
533         throw std::logic_error("type not found in collection");
534 }
535
536 template<typename T>
537 CollectionItemTypeBase *Collection::get_type(const std::string &name) const
538 {
539         for(CollectionItemTypeBase *t: types)
540                 if(dynamic_cast<CollectionItemType<T> *>(t))
541                         return t;
542         CollectionItemTypeBase *type = nullptr;
543         for(CollectionItemTypeBase *t: types)
544                 if(t->can_extract<T>())
545                 {
546                         if(!name.empty() && t->match_name(name))
547                                 return t;
548                         type = t;
549                 }
550         return type;
551 }
552
553 } // namespace DataFile
554 } // namespace Msp
555
556 #endif