ALib C++ Library
Library Version: 2412 R0
Documentation generated by doxygen
Loading...
Searching...
No Matches
configuration.cpp
1// #################################################################################################
2// ALib C++ Library
3//
4// Copyright 2013-2024 A-Worx GmbH, Germany
5// Published under 'Boost Software License' (a free software license, see LICENSE.txt)
6// #################################################################################################
8
9#if !DOXYGEN
15#endif // !DOXYGEN
16
17namespace alib {
18
19//==================================================================================================
20/// This is the namespace of\alibmod, which implements access, evaluation and writing of
21/// configuration variables. Variable data can be load from different sources, for example
22/// configuration files, command line parameters, environment variables, or any custom storage type.
23///
24/// Please consult \ref alib_mod_config "ALib Module Config - Programmer's Manual"
25/// for further information.
26//==================================================================================================
27namespace config {
28
29
30// =================================================================================================
31// Implementation of class detail::NodeMaintainer
32// =================================================================================================
33namespace detail
34{
35void ConfigNodeHandler::FreeNode( TTree& tree, typename TTree::Node& node )
36{
37 // delete node name
38 auto& cfg= static_cast<Configuration&>(tree);
39 cfg.Pool.free( const_cast<TTree::CharacterType*>(node.name.storage.Buffer()),
40 size_t(node.name.storage.Length()) * sizeof(TTree::CharacterType) );
41
42 // delete vdata
43 Entry& entry= node.data;
44 if( entry.data )
45 {
46 entry.meta->destruct(entry.data, cfg.Pool);
47 cfg.Pool().Free(entry.data, entry.meta->size() );
48 }
49}
50} // namespace detail
51
52// =================================================================================================
53// Implementation of class Configuration
54// =================================================================================================
56: StringTree ( allocator, '/')
57, Pool ( allocator )
58, types ( allocator )
59, replacementDeclarations( allocator )
60, listeners ( allocator )
61, BooleanTokens ( allocator )
62{
63 DbgSetDCSName("Configuration");
64
65 // Register built-in types
73
74 // read boolean false/true values from resources
75 if( createDefaults == lang::CreateDefaults::Yes)
76 {
77 Token tokenBuf[10];
78 Token::LoadResourcedTokens( CONFIG, "BTF", tokenBuf ALIB_DBG(, 10) );
79 for (int i = 0; i < 10; i+= 2 )
80 BooleanTokens.EmplaceBack(tokenBuf[i], tokenBuf[i+1]);
81
82 // create default plugins
83 auto& ma= GetAllocator();
84 InsertPlugin( environmentPlugin= ma().New<EnvironmentVariablesPlugin>(ma) );
85 InsertPlugin( cliPlugin= ma().New< CLIVariablesPlugin>(ma) );
86 }
87}
88
90{
91 // we have to delete all nodes before the invocation of the base destructor, because
92 // this would use our pool allocator on existing nodes (which is then destructed already).
94
95 ALIB_ASSERT_WARNING( listeners.IsEmpty(), "CONFIG",
96 "Remaining registered listeners when destruction configuration.")
97}
98
100 lang::ContainerOp insertOrRemove,
101 int event,
102 const Variable* variable,
103 const StringTree::Cursor* subTree,
104 const String& variableName,
105 const String& pathPrefixGiven,
106 const String& pathSubstring )
107{
108 // checks
109 ALIB_ASSERT_ERROR( variable==nullptr || variable->IsDeclared() , "CONFIG", "Given variable not declared.")
110 ALIB_ASSERT_ERROR( variable==nullptr || &variable->AsCursor().Tree() == this , "CONFIG", "Given variable does not belong to this configuration.")
111 ALIB_ASSERT_ERROR( subTree ==nullptr || subTree->IsValid() , "CONFIG", "Invalid cursor given." )
112 ALIB_ASSERT_ERROR( subTree ==nullptr || &subTree ->Tree() == this , "CONFIG", "Given cursor does not belong to this configuration.")
113
114 // remove / from path
115 const String pathPrefix= pathPrefixGiven.CharAtStart() == StringTree::Separator()
116 ? pathPrefixGiven.Substring(1)
117 : pathPrefixGiven;
118
119 // ---------------- registration ---------------------
120 if( insertOrRemove == lang::ContainerOp::Insert)
121 {
122 listeners.EmplaceBack( ListenerRecord{ listener,
123 event,
124 variable ? variable->AsCursor().Export() : ConstCursorHandle(),
125 subTree ? subTree-> Export() : ConstCursorHandle(),
128 AStringPA(Pool) } );
129 listeners.Back().variableName << variableName;
130 listeners.Back().pathPrefix << pathPrefix;
131 listeners.Back().pathSubstring<< pathSubstring;
132
133 return;
134 }
135
136 // ---------------- de-registration ---------------------
137 for (auto it= listeners.begin() ; it != listeners.end() ; ++it )
138 if( it->listener == listener
139 && it->event == event
140 && it->variable == ( variable ? variable->AsCursor().Export() : ConstCursorHandle() )
141 && it->subTree == ( subTree ? subTree ->Export() : ConstCursorHandle() )
142 && it->variableName .Equals( variableName )
143 && it->pathPrefix .Equals( pathPrefix )
144 && it->pathSubstring.Equals( pathSubstring ) )
145 {
146 (void) listeners.Erase( it );
147 return;
148 }
149
150 ALIB_WARNING( "CONFIG", "Listener with matching set of parameters not found with deregistration." )
151
152} // Configuration::registerListener
153
154
156{
157 // checks
158 ALIB_ASSERT_ERROR( listener!=nullptr, "CONFIG", "Given listener is nullptr.")
159
160 // ---------------- de-registration ---------------------
161 int cnt= 0;
162 for (auto it= listeners.begin() ; it != listeners.end() ; )
163 if( it->listener == listener )
164 {
165 it= listeners.Erase( it );
166 ++cnt;
167 }
168 else
169 ++it;
170
171 return cnt;
172} // Configuration::registerListener
173
175 const Variable& variable,
176 const String& variablePathGiven,
177 Priority previousPriority )
178{
179 String256 variablePathBuffer;
180 const String* variablePath= &variablePathGiven;
181 for (auto it= listeners.begin() ; it != listeners.end() ; ++it )
182 if( event == it->event )
183 {
184 // if needed generate variable path
185 if( variablePath->IsEmpty()
186 && ( it->variableName .IsNotEmpty()
187 || it->pathPrefix .IsNotEmpty()
188 || it->pathSubstring.IsNotEmpty() ) )
189 {
190 variablePathBuffer << variable;
191 variablePath= &variablePathBuffer;
192 }
193
194 if( ( it->variable .IsValid() && ( it->variable == variable.AsCursor().Export() ) )
195 || ( it->subTree .IsValid() && ( variable.AsCursor().Distance( ImportCursor(it->subTree) ) >= 0 ) )
196 || ( it->variableName .IsNotEmpty() && it->variableName.Equals(variable.AsCursor().Name()) )
197 || ( it->pathPrefix .IsNotEmpty() && variablePath->StartsWith(it->pathPrefix) )
198 || ( it->pathSubstring.IsNotEmpty() && variablePath->IndexOf(it->pathSubstring) >= 0 )
199 )
200 {
201 it->listener->Notify( variable,
203 previousPriority );
204 }
205 }
206} // Configuration::notifyListeners
207
208
209void Configuration::presetImportString(const String& name, const String& value,
210 const StringEscaper* escaper, Priority priority)
211{
212 auto cursor= Root();
213 cursor.GoToCreateChildIfNotExistent(A_CHAR("$PRESETS"));
214
215 // nullptr given? Delete a preset.
216 if( value.IsNull() )
217 {
218 if( cursor.GoTo(name).IsNotEmpty() )
219 return; // nothing was previously set
220
221 auto& entry= *cursor;
222 if( entry.priority > priority )
223 return; // do not delete if lower priority!
224
225 if( entry.data )
226 {
227 entry.meta->destruct( entry.data, Pool );
228 Pool().Free( entry.data, entry.meta->size() );
229 entry.meta= nullptr;
230 entry.data= nullptr;
231 entry.priority= Priority::NONE;
232 }
233
234 // delete this node, but only if it is a leaf.
235 if( !cursor.HasChildren() )
236 cursor.Delete();
237
238 return;
239 }
240
241
242 // set-mode
243 cursor.GoToCreatedPathIfNotExistent(name);
244
245 // create a fake variable. We do not do this using the variable class, because this would
246 // raise assertions.
247 auto& entry= *cursor;
248 if( entry.data == nullptr )
249 {
250 auto it= types.Find(A_CHAR("S"));
251 ALIB_ASSERT_ERROR( it != types.end(), "CONFIG",
252 "Variable type 'S' not registered. This usually cannot happen." )
253 auto* meta= entry.meta= *it;
254
255 // initialize fields
256 entry.data = reinterpret_cast<detail::VDATA*>(Pool().Alloc( meta->size(), alignof(detail::VDATA)));
257 meta->construct( cursor->data, Pool );
258 entry.priority= priority;
259 }
260
261 if( entry.priority <= priority )
262 {
263 entry.priority = priority;
264 entry.declaration= reinterpret_cast<const Declaration*>( escaper );
265 (Variable(cursor))= value;
266 }
267}
268
269// #############################################################################################
270// Declaration replacement allocation
271// #############################################################################################
272const Declaration* Configuration::StoreDeclaration( const Declaration* orig, const Box& replacements )
273{
274 //------------- prepare replacement -------------
275 String128 replace;
276
278 const Box* replacementPtr;
279 integer qtyReplacements;
280 if ( replacements.IsArrayOf<Box>() )
281 {
282 replacementPtr = replacements.UnboxArray<Box>();
283 qtyReplacements= replacements.UnboxLength();
284 }
285 else if ( replacements.IsType<BoxesHA*>() )
286 {
287 const auto* boxes= replacements.Unbox<BoxesHA*>();
288 replacementPtr = boxes->data();
289 qtyReplacements= boxes->Size();
290 }
291 else if ( replacements.IsType<BoxesMA*>() )
292 {
293 const auto* boxes= replacements.Unbox<BoxesMA*>();
294 replacementPtr = boxes->data();
295 qtyReplacements= boxes->Size();
296 }
297 else if ( replacements.IsType<BoxesPA*>() )
298 {
299 const auto* boxes= replacements.Unbox<BoxesPA*>();
300 replacementPtr = boxes->data();
301 qtyReplacements= boxes->Size();
302 }
303 else
304 {
305 replacementPtr = &replacements;
306 qtyReplacements= 1;
307 }
308
309 //------------- replace name -------------
310 String256 bufName; ALIB_DBG( bufName .DbgDisableBufferReplacementWarning() );
311 bufName << orig->EnumElementName;
312
313 for ( integer replCnt= 0; replCnt< qtyReplacements ; ++replCnt )
314 if ( !replacementPtr->IsType<void>() )
315 {
316 String64 search("%"); search._( replCnt + 1 );
317 replace.Reset( *( replacementPtr + replCnt) );
318 bufName .SearchAndReplace( search, replace );
319 }
320
321 // search in hashtable
322 {
323 auto it= replacementDeclarations.Find( bufName );
324 if( it != replacementDeclarations.end() )
325 return *it;
326 }
327
328 // not found, we have to create a new one
329 String1K bufComments; ALIB_DBG( bufComments .DbgDisableBufferReplacementWarning() );
330 String128 bufDefaultValue; ALIB_DBG( bufDefaultValue.DbgDisableBufferReplacementWarning() );
331 bufComments << orig->comments;
332 if ( orig->defaultValue.IsNotNull() )
333 bufDefaultValue << orig->defaultValue;
334
335 for ( integer replCnt= 0; replCnt< qtyReplacements ; ++replCnt )
336 if ( !replacementPtr->IsType<void>() )
337 {
338 String64 search("%"); search._( replCnt + 1 );
339 replace.Reset( *( replacementPtr + replCnt) );
340 bufComments .SearchAndReplace( search, replace );
341 bufDefaultValue.SearchAndReplace( search, replace );
342 }
343
345
346 // --------------------- create copy ---------------------
347 Declaration* result = GetAllocator()().New<Declaration>();
348 result->EnumElementName.Allocate(GetAllocator(), bufName);
349 result->typeName = orig->typeName;
350 result->defaultValue .Allocate(GetAllocator(), bufDefaultValue);
351 result->comments .Allocate(GetAllocator(), bufComments);
353
354 return result;
355}
356
357// #############################################################################################
358// Other tools
359// #############################################################################################
360std::pair<bool,int8_t> Configuration::ParseBooleanToken( const String& pValue )
361{
362 int8_t index= 0;
363 Substring value(pValue);
364 if( value.Trim().IsEmpty() )
365 return {false, int8_t(-1)};
366
367 for ( auto& it : BooleanTokens )
368 {
369 if ( it.first .Match(value) ) return {false, index};
370 if ( it.second.Match(value) ) return {true , index};
371 ++index;
372 }
373 return {false, int8_t(-1)};
374}
375
376AString& Configuration::WriteBooleanToken( bool value, int8_t idxRequested, AString& dest )
377{
378 // find the right pair of tokens
379 if(idxRequested < 0)
380 idxRequested= 0;
381
382 auto it= BooleanTokens.begin();
383 for( int8_t index=0 ; index < idxRequested && it != BooleanTokens.end() ; ++index )
384 ++it;
385
386 if( it == BooleanTokens.end() )
387 it= BooleanTokens.begin();
388
389 // use first (false) or second (true) to write
390 (!value ? it->first : it->second).GetExportName( dest );
391 return dest;
392}
393
395{
396DOX_MARKER( [DOX_CONFIG_DELETE_SAMPLE] )
397// get root node of the tree
398Cursor cs= Root();
399
400// try to walk the givedn path. If a remainder of the string exits, exit.
401if( cs.GoTo(path).IsNotEmpty() )
402 return false;
403
404// now delete the subtree, including the node the cursor represents
405cs.Delete();
406
407// that's it
408return true;
409DOX_MARKER( [DOX_CONFIG_DELETE_SAMPLE] )
410}
411
412}} // namespace [alib::config]
413
414
415// #################################################################################################
416// struct T_Append<Variable>
417// #################################################################################################
418#if !DOXYGEN
419
420namespace alib::strings {
421
422void T_Append<config::Variable,nchar, lang::HeapAllocator>::operator()( TAString<nchar, lang::HeapAllocator>& target, const config::Variable& variable )
423{
424#if ALIB_CHARACTERS_WIDE
425 String256 name;
426 variable.Name(name);
427 target << name;
428#else
429 variable.Name(target);
430#endif
431
432}
433
434void T_Append<config::Variable,wchar, lang::HeapAllocator>::operator()( TAString<wchar, lang::HeapAllocator>& target, const config::Variable& variable )
435{
436#if ALIB_CHARACTERS_WIDE
437 variable.Name(target);
438#else
439 String256 name;
440 variable.Name(name);
441 target << name;
442#endif
443}
444
445
446} // namespace [alib::strings]
447#endif // ALIB_DOX
448
449
TElementType * UnboxArray() const
Definition box.inl:987
integer UnboxLength() const
Definition box.inl:1018
bool IsType() const
bool IsArrayOf() const
Definition box.inl:725
const TUnboxable Unbox() const
integer Size() const
Definition boxes.inl:176
ALIB_API ~Configuration()
Destructor.
EnvironmentVariablesPlugin * environmentPlugin
A default plugin created and inserted with construction.
TypesHashTable types
A hashtable for the registered types. Key is the type name, value is the VMeta singleton.
ALIB_API void notifyListeners(int event, const Variable &variable, const String &variablePath, Priority previousPriority)
ALIB_API std::pair< bool, int8_t > ParseBooleanToken(const String &src)
ALIB_API AString & WriteBooleanToken(bool value, int8_t index, AString &dest)
ALIB_API void registerListener(ConfigurationListener *listener, lang::ContainerOp insertOrRemove, int event, const Variable *variable, const StringTree::Cursor *subTree, const String &variableName, const String &pathPrefix, const String &pathSubstring)
ALIB_API const Declaration * StoreDeclaration(const Declaration *orig, const Box &replacements)
ALIB_API Configuration(MonoAllocator &allocator, lang::CreateDefaults createDefaults=lang::CreateDefaults::Yes)
List< MonoAllocator, ListenerRecord > listeners
The list of registered listeners.
ALIB_API bool DeletePath(const String &path)
List< MonoAllocator, std::pair< Token, Token >, Recycling::None > BooleanTokens
friend class Variable
Type alias in namespace alib.
CLIVariablesPlugin * cliPlugin
A default plugin created and inserted with construction.
ALIB_API int MonitorStop(ConfigurationListener *listener)
ALIB_API void presetImportString(const String &name, const String &value, const StringEscaper *escaper, Priority priority)
VMeta * meta
The virtual handler instance for this variable.
VDATA * data
The list hook of values.
Iterator EmplaceUnique(TArgs &&... args)
Iterator Find(const KeyType &key)
SubstringType GoTo(const NameType &path)
int Distance(const TCursor< true > &other) const
AllocatorType & GetAllocator() noexcept
void DbgSetDCSName(const char *name) const
constexpr CharacterType Separator() const noexcept
Cursor ImportCursor(CursorHandle handle)
void InsertPlugin(ConfigurationPlugin *plugin, lang::Responsibility responsibility=lang::Responsibility::KeepWithSender)
void free(void *mem, size_t size)
ALIB_API integer SearchAndReplace(TChar needle, TChar replacement, integer startIdx=0, integer endIdx=strings::MAX_LEN)
TAString & _(const TString< TChar > &src, integer regionStart, integer regionLength=MAX_LEN)
void DbgDisableBufferReplacementWarning()
Definition tastring.inl:363
constexpr bool IsNull() const
Definition string.hpp:364
integer IndexOf(TChar needle, integer startIdx=0) const
Definition string.hpp:896
constexpr bool IsEmpty() const
Definition string.hpp:383
void Allocate(TAllocator &allocator, const TString< TChar > &copy)
Definition string.hpp:2012
TChar CharAtStart() const
Definition string.hpp:466
constexpr bool IsNotNull() const
Definition string.hpp:371
TString< TChar > Substring(integer regionStart, integer regionLength=MAX_LEN) const
Definition string.hpp:406
bool StartsWith(const TString &needle) const
Definition string.hpp:820
TSubstring & Trim(const TCString< TChar > &whiteSpaces=TT_CStringConstants< TChar >::DefaultWhitespaces())
static ALIB_API void LoadResourcedTokens(lang::resources::ResourcePool &resourcePool, const NString &resourceCategory, const NString &resourceName, strings::util::Token *target, int dbgSizeVerifier, character outerSeparator=',', character innerSeparator=' ')
#define ALIB_WARNING(...)
Definition alib.hpp:1268
#define A_CHAR(STR)
#define ALIB_WARNINGS_RESTORE
Definition alib.hpp:849
#define ALIB_ASSERT_ERROR(cond,...)
Definition alib.hpp:1271
#define ALIB_WARNINGS_ALLOW_UNSAFE_BUFFER_USAGE
Definition alib.hpp:760
#define ALIB_ASSERT_WARNING(cond,...)
Definition alib.hpp:1272
#define ALIB_DBG(...)
Definition alib.hpp:390
CreateDefaults
Denotes whether default entities should be created or not.
@ Yes
Create default values.
ContainerOp
Denotes standard container operations.
@ Insert
Denotes insertions.
Definition alib.cpp:69
config::ConfigCamp CONFIG
The singleton instance of ALib Camp class ConfigCamp.
LocalString< 256 > String256
Type alias name for TLocalString<character,256>.
lang::integer integer
Type alias in namespace alib.
Definition integers.hpp:273
strings::TAString< character, PoolAllocator > AStringPA
Type alias in namespace alib.
Definition astringpa.hpp:45
Event
The type of change that imposes the notification of a listener.
Record used to manage registered listeners.
virtual size_t size()=0
virtual void destruct(void *memory, PoolAllocator &pool)=0
static ALIB_API void FreeNode(TTree &tree, TTree::Node &node)
A handle type used with methods TCursor::Export and ImportCursor.
T data
The templated custom data object stored with each node.
typename TNodeHandler::CharacterType CharacterType
String EnumElementName
The name of the enum element.
Definition records.hpp:660
void operator()(TAString< TChar > &target, const TAppendable &src)
NameStorageType storage
The name when stored in the hashtable.