1 /**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 XMLMethodRegistry.h
6
7 Paul Licameli
8
9 **********************************************************************/
10
11 #ifndef __AUDACITY_XML_METHOD_REGISTRY__
12 #define __AUDACITY_XML_METHOD_REGISTRY__
13
14 #include <forward_list>
15 #include <string>
16 #include <string_view>
17 #include <functional>
18 #include <type_traits>
19 #include <unordered_map>
20 #include <utility>
21 #include <vector>
22
23 class XMLTagHandler;
24 class XMLWriter;
25 class XMLAttributeValueView;
26
27 //! Implementation helper for ProjectFileIORegistry.
28 /*! It makes most of the work non-inline and is used by derived classes that
29 supply thin inline type-erasing wrappers. */
30 class XML_API XMLMethodRegistryBase {
31 public:
32
33 //! A helper type alias for a function taking a structure and a string value
34 template< typename Substructure >
35 using Mutator = std::function< void(Substructure&, const XMLAttributeValueView &) >;
36
37 //! A helper type alias for a list of mutators, associated with tag strings
38 template< typename Substructure >
39 using Mutators = std::vector< std::pair< std::string, Mutator<Substructure> > >;
40
41 XMLMethodRegistryBase();
42 ~XMLMethodRegistryBase();
43 protected:
44 using TypeErasedObjectAccessor = std::function< XMLTagHandler *( void* ) >;
45 using TagTable =
46 std::unordered_map< std::string_view, TypeErasedObjectAccessor >;
47 TagTable mTagTable;
48 std::forward_list<std::string> mTags;
49
50 void Register(std::string tag, TypeErasedObjectAccessor accessor);
51 XMLTagHandler *CallObjectAccessor( const std::string_view &tag, void *p );
52
53 using TypeErasedAccessor = std::function< void*( void* ) >;
54 using TypeErasedAccessors = std::vector< TypeErasedAccessor >;
55 TypeErasedAccessors mAccessors;
56
57 void PushAccessor( TypeErasedAccessor accessor );
58
59 using TypeErasedMutator = std::function< void( void*, const XMLAttributeValueView& ) >;
60 //! From attribute name, to index in accessor table with a mutator
61 using MutatorTable = std::unordered_map<std::string_view,
62 std::pair<size_t, TypeErasedMutator>>;
63 MutatorTable mMutatorTable;
64 std::forward_list<std::string> mMutatorTags;
65
66 void Register(std::string tag, TypeErasedMutator mutator);
67
68 bool CallAttributeHandler(const std::string_view& tag,
69 void *p, const XMLAttributeValueView &value );
70
71 using TypeErasedWriter = std::function< void(const void *, XMLWriter &) >;
72 using WriterTable = std::vector< TypeErasedWriter >;
73
74 WriterTable mAttributeWriterTable;
75 void RegisterAttributeWriter( TypeErasedWriter writer );
76 void CallAttributeWriters( const void *p, XMLWriter &writer );
77
78 WriterTable mObjectWriterTable;
79 void RegisterObjectWriter( TypeErasedWriter writer );
80 void CallObjectWriters( const void *p, XMLWriter &writer );
81 };
82
83 /*! A class template of inline type-erasing wrapper functions, but one function
84 with linkage is also needed. See the macros that help generate that function.
85 */
86 template< typename Host >
87 class XMLMethodRegistry : public XMLMethodRegistryBase {
88 public:
89
90 // Typically statically constructed
91 struct ObjectReaderEntry {
92 template <
93 /*!
94 This "accessor" may or may not act as a "factory" that builds a new object and
95 may return nullptr. Caller of the accessor is not responsible for the object
96 lifetime, which is assumed to outlast the project loading procedure.
97 */
98 typename ObjectAccessor /*!< Often a lambda.
99 A function from Host& to XMLTagHandler*, maybe returning null */
100 >
ObjectReaderEntryObjectReaderEntry101 ObjectReaderEntry( const std::string &tag, ObjectAccessor fn )
102 {
103 // Remember the function, type-erased
104 Get().Register( tag, [ fn = std::move(fn) ] (void *p) {
105 // CallObjectAccessor will guarantee p is not null
106 return fn( *static_cast<Host *>(p) );
107 } );
108 }
109 };
110
CallObjectAccessor(const std::string_view & tag,Host & host)111 XMLTagHandler *CallObjectAccessor(const std::string_view& tag, Host& host)
112 {
113 return XMLMethodRegistryBase::CallObjectAccessor( tag, &host );
114 }
115
116 /*! Typically statically constructed */
117 /*!
118 Registers procedures to update the host for certain attributes, in two
119 steps: first the `accessor` which fetches some substructure owned by the
120 host, which is the common step; then, the `mutators` that rewrite the
121 substructure, each of them associated with one attribute string, and
122 taking a reference to the substructure and a value string.
123 */
124 struct AttributeReaderEntries {
125 template<
126 typename Accessor, /*!< Often a lambda.
127 Takes const Host& and returns Substructure& */
128 typename Substructure //<! Type deduction of the return of Accessor
129 = std::remove_reference_t< decltype(
130 std::declval<Accessor>()( std::declval<Host &>() )
131 ) >
132 >
AttributeReaderEntriesAttributeReaderEntries133 AttributeReaderEntries( Accessor fn, Mutators<Substructure> pairs )
134 {
135 // Remember the functions, type-erased
136 auto ®istry = Get();
137 registry.PushAccessor(
138 [ fn = std::move(fn) ] ( void *p ) {
139 // CallAttributeHandler will guarantee p is not null
140 return &fn( *static_cast<Host *>(p) ); }
141 );
142 for (auto &pair : pairs)
143 registry.Register( pair.first,
144 [ fn = move(pair.second) ]( auto p, auto value ){
145 fn( *static_cast<Substructure*>(p), value ); }
146 );
147 }
148 };
149
150 // @return whether any function was found and called for the tag
CallAttributeHandler(const std::string_view & tag,Host & host,const XMLAttributeValueView & value)151 bool CallAttributeHandler(
152 const std::string_view &tag, Host &host, const XMLAttributeValueView& value )
153 {
154 return XMLMethodRegistryBase::CallAttributeHandler( tag, &host, value );
155 }
156
157 //! Typically statically constructed
158 struct AttributeWriterEntry {
159 template <
160 /*!
161 The Writer may write any number of XML attributes, but must not write tags.
162 So there should be some reader entries corresponding to each writer, and that
163 may be many-to-one.
164 The readers must not make assumptions about the sequence in which they will
165 be called.
166 */
167 typename Writer /*!< Often a lambda.
168 Takes const Host& and XMLWriter& and returns void */
169 >
AttributeWriterEntryAttributeWriterEntry170 explicit AttributeWriterEntry( Writer fn )
171 {
172 // Remember the function, type-erased
173 Get().RegisterAttributeWriter(
174 [ fn = std::move(fn) ] ( const void *p, XMLWriter &writer ) {
175 // CallObjectAccessor will guarantee p is not null
176 return fn( *static_cast<const Host *>(p), writer );
177 } );
178 }
179 };
180
181 //! Typically statically constructed
182 struct ObjectWriterEntry {
183 template <
184 /*!
185 The Writer may write any number of XML tags, but no attributes.
186 So there should be some reader entries corresponding to each writer, and that
187 may be many-to-one.
188 The readers must not make assumptions about the sequence in which they will
189 be called.
190 */
191 typename Writer /*!< Often a lambda.
192 Takes const Host& and XMLWriter& and returns void */
193 >
ObjectWriterEntryObjectWriterEntry194 explicit ObjectWriterEntry( Writer fn )
195 {
196 // Remember the function, type-erased
197 Get().RegisterObjectWriter(
198 [ fn = std::move(fn) ] ( const void *p, XMLWriter &writer ) {
199 // CallObjectAccessor will guarantee p is not null
200 return fn( *static_cast<const Host *>(p), writer );
201 } );
202 }
203 };
204
CallAttributeWriters(const Host & host,XMLWriter & writer)205 void CallAttributeWriters( const Host &host, XMLWriter &writer )
206 {
207 XMLMethodRegistryBase::CallAttributeWriters( &host, writer );
208 }
209
CallObjectWriters(const Host & host,XMLWriter & writer)210 void CallObjectWriters( const Host &host, XMLWriter &writer )
211 {
212 XMLMethodRegistryBase::CallObjectWriters( &host, writer );
213 }
214
215 //! Get the unique instance
216 static XMLMethodRegistry &Get();
217
218 };
219
220 /*! Typically follows the `using` declaration of an XMLMethodRegistry
221 specialization; DECLSPEC is for linkage visibility */
222 #define DECLARE_XML_METHOD_REGISTRY(DECLSPEC, Name) \
223 template<> auto DECLSPEC Name::Get() -> Name &;
224
225 /*! Typically in the companion .cpp file */
226 #define DEFINE_XML_METHOD_REGISTRY(Name) \
227 template<> auto Name::Get() -> Name & \
228 { \
229 static Name registry; \
230 return registry; \
231 }
232
233 #endif
234