ALib C++ Library
Library Version: 2312 R0
Documentation generated by doxygen
ALib Module Resources - Programmer's Manual

1. Introduction

ALib uses the term "resources" for string data that technically may be defined in a constant and static fashion, but that a software volunteers to make configurable. Typical samples of data that a software exposes for external management are "themes" ( color and font schemes), or so called "externalized strings", which mostly may be used to translate a software into a different "locale" and human language.

While the conceptual definition of resources is very similar to the concept of configuration data, it is good practice to thoroughly decide for each specific piece of data that is deemed to be made configurable, whether it is resource or configuration data. While the mentioned samples of language translation strings and color schemes are typically resources, a server name that a software attaches to is typically configuration data. End users commonly know and understand the difference between the two intuitively and appreciate if the corresponding data sources are separated, especially as this way, resource data does not "clutter" the definitions of configuration sources.

The best rule of thumb to differentiate resource from configuration data is to check if it is possible to distribute a software without turning the data in question into either of the two externalization concepts, thus "hard coding" the data. If so, the data more likely is resource data. In the sample of the "server address" above, this is not possible if such server address targets a machine that is under the control of an end user. However, if it is a machine provided by the distributor of the software which is the same with any installation, then the concept of resourced data rather fits.

Note
The concept of configuration data is implemented with module ALib Configuration.

2. Resources and Modules

As documented in chapter 2. ALib Modules of the ALib Programmer's Manual, the various ALib Modules can be separated into "full" modules and those which are not. In this module dependency graph it could be noted that all "full modules" (dark blue) are dependent on module ALib Resources, discussed in this manual.

This relation is explained as follows:

  • All high-level ALib modules need externalized string resources (e.g. for ALib Exceptions, ALib Enum Records, etc.).
  • Resources need a controlled process of bootstrapping a software.
  • Library class Module holds a pointer to an object of type ResourcePool.
  • Class Module provides strictly defined and phased bootstrapping mechanics, especially designed to support the customization of resource management.

Therefore, all "full" ALib modules not only depend on module ALib Resources, but also dispose about a singleton type derived from core library class Module.

3. Data Contract / Conceptual Invariants

ALib resources implemented with this module, conceptually impose a set of rules which may be named a "contract", "invariants" or just "determinations". These rules are given in the following subchapters.

3.1 Resource Keys

Key values to resources are defined as a two level hierarchy of a category and a name. This determination complies with how configuration data is addressed with module ALib Configuration. This is also in alignment with common 3rd-party resource management systems established in the industry.

Independent from the compilation options of ALib in respect to choosing the default character width, the string type of the category and name is fixed to NString, hence using the narrow nchar type. This is not in alignment with configuration data as defined in ALib Configuration. The rational for this is that while configuration data categories and names may be translated themselves to localized versions (by defining those strings as resources!), the category and name strings of the resources are deemed to be hard-coded in the source code that accesses resources. As such, they are not shared with end-users, are never localized and should be using the plain 7-bit printable ASCII character set.

3.2 Restricted To String Type

All resource data is of String type (of compilation-dependent character width).

This restriction imposes that any other data type (for example color codes) has to be de-serialized (decoded) when resourced.

3.3 Static Data

Although resource data technically is non static data, conceptually with this implementation it is.

This determination has the following impacts:

  • Resources do not have to be "acquired" and "released" when accessed, which tremendously simplifies the use of resources.
  • A using code can rely on the fact that the life-cycle of the string buffers of accessed resources is infinite. No local copies of the string data is required. (Note, that due to the missing option of acquisition/release of resources, copying a resource string would not even be possible.)
  • Resource data does not dynamically change during the life-cycle of a software process. In other words, accessing resource data of a specific category and name, will always result in the same returned value.
  • Consequently, an implementation of abstract interface type ResourcePool that attaches to a 3rd-party resource system which supports dynamic resources, usually has to create a copy of the data returned from the 3rd-party system and return this copy instead of the original value.
Note
The latter point is implemented with built-in, alternative resource type ConfigResourcePool, which relies on module ALib Configuration to maintain the resources externally. Configuration data implemented with this module is not static and thus may change during the life-cycle of a software. Consequently, this type uses monotonic allocation with type HashTable to store persistent copies of each resource, at the moment it is accessed the first time. With subsequent requests, the same cached data is returned.

3.4 Thread Safeness

Accessing resources using abstract interface method ResourcePool::Get is a thread-safe operation.

In contrast to this, an invocation to any of the methods that define resources, namely ResourcePool::Bootstrap and ResourcePool::BootstrapBulk is not a thread-safe operation. This is true in respect to each other as well - and most important - also in respect to parallel resource access with method Get.

This determination has the following impacts:

  • Interface methods prefixed with the term Bootstrap of abstract type ResourcePool are deemed to exclusively being invoked during bootstraping of ALib as well as the of the according software using ALib. Such bootstrapping has to be performed prior to starting any threads that potentially modify resources.
  • Interface method Get of abstract type ResourcePool may be called in parallel from different execution threads, without the need of locking a specified mutex.
  • An implementation of interface ResourcePool may need to impose an internal protection in respect to race conditions of multi-threaded access.
Note
Class LocalResourcePool, which is used by default if bootstrapping of ALib is not customized accordingly, is not in the need of using an internal mutex. This arises from the fact that after the single-threaded bootstrap finished, only read-operations on the resource pool are performed. The type just does not provide any other feature.
In contrast, class ConfigResourcePool, which relies on module ALib Configuration to manage the resources, takes internal precautions against race conditions of multi-threaded access, because configuration data may change during the life-cycle of a software. Changes are imposed by the (custom) plug-ins that are attached to its configuration object or by other code entities that are allowed to access the public configuration object directly.

4. Interface Class ResourcePool

The central type of the module, class ResourcePool was already mentioned several times. It constitutes a pure abstract interface. Due to the determinations of the concept given in previous chapter 3. Data Contract / Conceptual Invariants, its interface is very simple especially in respect to accessing resources, which is exclusively done with method ResourcePool::Get.

A user of ALib should have no bigger effort to implement this interface and adopt her own or any 3rd-party "backend" that performs the resource management and the externalization of strings.

Apart from that, two implementations of the interface are provided with ALib. Those are quickly introduced in the following sections.

4.1 Class LocalResourcePool

As explained above, an implementation of interface ResourcePool has to be constructed during bootstrap of ALib and distributed among the modules.

In case the bootstrap process is not customized, an instance of class LocalResourcePool is created and shared.

This class does not allow any externalization of resources and simply stores the given pointers to the static data in a HashTable, using monotonic allocation.

4.2 Class ConfigResourcePool

A second built-in implementation of class ResourcePool which can be created and shared among the modules of ALib by customizing the bootstrap process of the library, is given with class ConfigResourcePool.

The type externalizes resources by using entities found with module ALib Configuration. For this, it disposes about a public member of type Configuration and installs one InMemoryPlugin with default priority. This plug-in fetches all resources fed with methods Bootstrap and BootstrapBulk.

If after creation of an instance of the type, this instance is not changed and just used, then it behaves in an identical way as type LocalResourcePool (with only weaker performance and increased memory overhead).

Externalization is achieved when a custom plug-in is attached to the configuration object found with field ConfigResourcePool::Config. For example, this could be of type IniFile and be attached with priority Priorities::Standard.

With that, any entries made in that INI-file, would simply overwrite the corresponding resource values which use the lower default priority.

With this type a very simple but yet powerful way of externalizing resource data is given. The type is suitable in any situation where no other ("more professional") 3rd-party "backend" for resource management is available.
Here are some tips for the usage:

  • Method Configuration::FetchFromDefault may be used to create (for example) an INI-file that contains all resources that have been defined during bootstrap of a software.
  • Such INI-file could then be used as a "jump-start" for translating a software from its (hard-coded ) default language into a different one.
  • With the prioritization mechanics of class Configuration it is allowed to attach two or more INI-files, in parallel. This allows for example to have language translations of resources in place, which - if only sparsely defined - automatically fallback through the different configuration plug-ins until a translation is found, or - if none is found - fall back to the defaults stored in the InMemoryPlugin and initially fed with ResourcePool::BootstrapBulk.
    In other words, language translations can be stacked and software might allow to use not only specify a preferred language but a prioritized list of preferred languages.
  • Similar to the previous point two different configuration plug-ins (e.g. INI-files) could be used and have one storing only translated resources, the second all resources relevant for an application's color scheme ("Theme"). This way, language and color scheme can be chosen independency from each other.
  • Finally, this class also provides an alternative to implementing an own version of interface class ResourcePool from scratch. Precisely, instead of implementing interface ResourcePool, a may implement interface ConfigurationPlugin and attach this to field ConfigResourcePool::Config.
    One of the advantages of this approach would be that not all resource data needs to reside in the attached "backend". Some data might also reside in a different backend (with a next plug-in), or again in an INI-file.
Note
Despite its flexibility, type ConfigResourcePool is provided for convenience. It was just a "low hanging fruit" of implementation making use of sibling module ALib Configuration.
There are no plans on the road map of ALib to impose a more sophisticated implementation for externalized string resources. It is simply not in the domain of this library to provide a higher level of functionality. The whole purpose of this module itself ist to have an abstract and very simple interface into any sort of "resources backend" and its choice is completely independent from the choice of using ALib with a software!

5. Indirect Resource Access

5.1 TMP Struct T_Resourced

Sometimes it is required to define resource information, namely

  • the ResourcePool instance,
  • the resource category and
  • the resource name

for use by other components. TMP struct T_Resourced may be specialized to do such definition for C++ types. A specialization of the struct can be easily implemented using macro ALIB_RESOURCED.

A sample for the use of this struct is given with module ALib Configuration: To load and store configuration data, this module exposes a type of ALib Enum Records and accepts custom enumeration types in various interface methods, if they just have this specific record type associated.

Now, if an element of a custom enumeration type that disposes about a specialization of T_Resourced is passed to such interface method, internally this information is used to load further details of the variable from the resource pool.

5.2 Helper Struct Resourced

As soon as struct T_Resourced is specialized for a type, helper struct static struct ResourcedType becomes available.

The type has two overloaded methods Get: The first is parameterless and simply receives the resource string associated to a type with the specialization of T_Resourced. The second takes a replacement for the resource name. This may be used to retrieve resource strings which are likewise associated to the type.

Furthermore, the struct provides methods TypeNamePrefix and TypeNamePostfix which are meant to provide a standardized way to define a type's name using resources. The methods are for example used with specializations T_Append<TEnum,TChar> and T_Append<TEnumBitwise,TChar> which write enum element names into instances of type AString.

5.3 Helper Struct ResourceInfo

A next helper struct is given with ResourceInfo which first of all is a simple struct that stores resourcing information (the resource pool and category and name strings) for later use.

While this struct is usable without a specialization of T_Resourced, in most use cases it is, because it allows to convert the compile-time information given by T_Resourced into run-time information.

6. Further Details

6.1 Resourced Data Records And Tables

Besides just externalizing strings, many use cases require to access externalized data sets or even whole tables of this.

ALib module ALib Enums provides a solution for this with its concept ALib Enum Records. There a table of data is addressed using the C++ type information of enum types. Single records of a table may (or may not) be addressed by elements of the corresponding enumeration. The module provides convenient facilities the fields of the records and whole tables from resourced strings.

Before you go ahead and implement your "own" solution for externalized static data, it might be worthwhile to check out if ALib Enum Records meet your requirements.

6.2 Exporting Resources For Externalization

When resources are externalized, for example for translation to different human languages, the list of resources have to be imported to the external "backend". To do so all resources have to be queried from the library.

Here is a sample program that performs this task:

using namespace aworx;
using namespace std;
int main( int argc, const char *argv[] )
{
// create and set resource pool that uses a configuration file
// bootstrap alib
ALIB.Bootstrap( argc, argv );
// we externalize the string value, e.g. replacing "\" by "\\" and this way "\n" by "\\n"
// This might not be wanted for custom exports, but works well for ALib INI-files.
AString externalizedValue;
// loop over sections of the plugin
auto& sections= pool.Config.GetPluginTypeSafe<InMemoryPlugin>(
Priorities::DefaultValues )
->Sections();
for( auto& section : sections )
{
// no entries in section? (happens only for first, empty category)
if( section.Entries().IsEmpty() )
continue;
// write category
cout << endl << '[' << section.Name() << ']' << endl;
// loop over resources in actual category
for( auto& entry : section.Entries() )
{
// externalize and write
externalizer.ExternalizeValue( entry.Value, externalizedValue.Reset() , 0 );
cout << entry.Name() << '=' << externalizedValue << endl;
}
}
// shutdown alib
return 0;
}

The output of this sample can directly be loaded by class IniFile, hence with a plug-in attached to an instance of built-in resource pool implementation ConfigResourcePool. The sample might be adopted accordingly to write a format suitable to be imported to the backend of choice.

With every update of the library, changes of the resource strings have to be determined. This might be done for example with a custom unit test that compares the default entries with the ones currently stored in an external backend. New resources might be added, others might disappear, and worst: existing ones might change their content format. In the latter case, an externalized resource might be errorneous and lead to undefined behavior.

Starting with library version 1903, to support the detection of changes, the version history is found in

    ALIB_BASE_DIR/docs/pages/resource-exports/

The following exports are available:

6.3 Debug- And Usage Statistics

With the provision of compiler symbol ALIB_DEBUG_RESOURCES, static field LocalResourcePool::DbgResourceLoadObserver becomes available. If set to &std::cout prior to bootstrapping ALib, the resource load process can be observed on the console, because methods LocalResourcePool::BootstrapBulk and LocalResourcePool::BootstrapAddOrReplace will write information on bulk and singular resource data definitions. This tremendously helps to find errors in resource strings, which often are simple missing commas and similar.

Next, virtual method ResourcePool::DbgGetCategories and ResourcePool::DbgGetList become available. The latter returns a list of all resources defined, including a usage counter. The method is only implemented by class LocalResourcePool. Alternative type ConfigResourcePool does not implement it.

Furthermore, convenience method ResourcePool::DbgDump becomes available which simply writes the list of symbols into an AString, sorted by category and in an alphabetical order.

The usage counter included in the list may be used to identify two groups of resource strings:

  • Resource strings that never have been loaded, and
  • Resource strings that have been loaded very often during the actual execution of a software.

While entries of the first type may have become obsolete and are candidates for removal, those of the second type, a software might consider to "cache" the symbol in a variable instead of repeatedly retrieving it from the resource pool.

Remember: While trivial implementation class LocalResourcePool is very fast and resource access is not really noticeable, other implementations might not be.

The following code snippets taken from the ALib unit tests, demonstrate how to quickly leverage these debug features, depending on compiler symbol ALIB_DEBUG_RESOURCES. The snippets might be copied to own projects and remain there forever.

Bootstrapping may look as follows:

#if ALIB_DEBUG_RESOURCES
#endif
int main( int argc, char **argv )
{
#if ALIB_DEBUG_RESOURCES
lib::resources::LocalResourcePool::DbgResourceLoadObserver= &std::cout;
#endif
aworx::ALIB.Bootstrap( argc, argv, BootstrapPhases::PrepareConfig );
//...
//...

Before termination of software (hence, prior to invoking ALIB.Shutdown()) The following code may be placed:

#if ALIB_DEBUG_RESOURCES && ALIB_ALOX
Log_Info( "---------------- Resource Pool Dump ----------------" )
auto categoryList= ALIB.GetResourcePool().DbgGetCategories();
integer sum= 0;
for( auto& category : categoryList )
{
Log_Info( "Resource category {:10} has {:3} entries", category.first, category.second )
sum+= category.second;
}
Log_Info( "This sums up to ", sum, " resource definitions" )
auto resourceList= ALIB.GetResourcePool().DbgGetList();
Log_Info( ResourcePool::DbgDump( resourceList ) )
Log_Info( "---------------- Resource Pool Dump (end) ----------" )
#endif

7. Sample

A comprehensive sample of using ALib resources placed in a custom module is provided with the tutorial of ALib Module 'CLI'. The sample code provided there, can be easily used as a jump start into an own project that creates a custom ALib module and leverages the resource features provided.

aworx::lib::config::XTernalizer
Definition: config/plugins.hpp:64
localresourcepool.hpp
aworx::lib::integer
platform_specific integer
Definition: integers.hpp:49
aworx::lib::ALibDistribution::CheckDistribution
ALIB_API void CheckDistribution(int alibVersion=ALIB_VERSION, uint64_t compilationFlags=ALIB_COMPILATION_FLAGS)
Definition: distribution.cpp:612
aworx::lib::resources::ConfigResourcePool::Config
config::Configuration Config
Definition: configresourcepool.hpp:71
distribution.hpp
aworx::lib::strings::TAString::Reset
TAString & Reset()
Definition: astring.hpp:1304
aworx::lib::config::InMemoryPlugin
Definition: inmemoryplugin.hpp:47
aworx::lib::Module::Bootstrap
ALIB_API bool Bootstrap(BootstrapPhases targetPhase=BootstrapPhases::Final, int argc=0, const char **argvN=nullptr, const wchar_t **argvW=nullptr)
Definition: module.cpp:64
configresourcepool.hpp
aworx::lib::Module::Shutdown
ALIB_API void Shutdown(ShutdownPhases targetPhase=ShutdownPhases::Destruct)
Definition: module.cpp:249
aworx::lib::resources::ConfigResourcePool
Definition: configresourcepool.hpp:43
aworx
Definition: alox/alox.hpp:40
aworx::lib::detail::PluginContainer::GetPluginTypeSafe
TPluginType * GetPluginTypeSafe(TPriorities priority)
Definition: lib/fs_plugins/plugins.hpp:289
aworx::ALIB
lib::ALibDistribution ALIB
Definition: distribution.cpp:125
aworx::lib::strings::TAString< character >
aworx::lib::config::XTernalizer::ExternalizeValue
virtual ALIB_API void ExternalizeValue(const String &src, AString &dest, character delim)
Definition: plugins.cpp:72
Log_Info
#define Log_Info(...)
Definition: macros.inl:68
aworx::lib::Module::BootstrapSetResourcePool
void BootstrapSetResourcePool(resources::ResourcePool *pool)
Definition: module.hpp:424
aworx::lib::boxing::compatibility::std::BootstrapStdStringBoxing
void BootstrapStdStringBoxing()
Definition: std_boxing.hpp:197
aworx::ResourcePool
lib::resources::ResourcePool ResourcePool
Type alias in namespace aworx.
Definition: resources.hpp:620
std_strings_iostream.hpp