Previous Up Next

Chapter 2  The Configuration Class

2.1  The ConfigurationException Class

An exception of type ConfigurationException is thrown if any Config4Cpp operation fails. The public API of this class is shown in Figure 2.1.

Figure 2.1: The ConfigurationException class
// Access with #include <config4cpp/ConfigurationException.h>
// or          #include <config4cpp/Configuration.h>

class ConfigurationException
{
public:
    ConfigurationException(const char * str);
    ConfigurationException(const ConfigurationException & other);
    ~ConfigurationException();
    const char * c_str() const;
};

Application code can access a string description of the exception by invoking the c_str() operation, as shown below:

try { ... } catch(const ConfigurationException & ex) {
    cerr << ex.c_str() << endl;
}

As explained in Section 1.4, to avoid having a dependency on either classic or standard IO streams, Config4Cpp is not implemented with the IO Stream library. Because of this, Config4Cpp does not define an operator for streaming a ConfigurationException to an output stream. There is nothing preventing you from defining such a streaming operator in the global scope in your own applications. Alternatively, you can stream an exception to an output stream by explicitly invoking the c_str() operation, as shown in the previous example.

2.2  The create() and destroy() Operations

Figure 2.2 shows the operations that are used to create and destroy a Configuration object.

Figure 2.2: Initialization and destruction APIs for Configuration
// Access with #include <config4cpp/Configuration.h>

class Configuration {
public:
    static Configuration * create();
    virtual void destroy();
    ...
};

You use the static create() operation to create a Configuration object. A newly created Configuration object is empty initially. You can then populate it and access its contents, as I will discuss in the following sections of this chapter. Finally, you should call destroy() to reclaim the memory of the Configuration object.

The correct behaviour of Config4Cpp depends on the locale being set correctly. Because of this, it is advisable to call setlocale() before invoking any Config4Cpp APIs. If you do this, then Config4Cpp will be able to handle characters defined in your locale, such as European accented characters or Japanese ideographs. If you neglect to call setlocale(), then Config4Cpp is likely to correctly process only characters in the 7-bit US ASCII character set. Figure 2.3 illustrates how to call setlocale(), create() and destroy().

Figure 2.3: Example of creating and destroying a Configuration object
#include <locale.h>
#include <config4cpp/Configuration.h>
using namespace config4cpp;
...
setlocale(LC_ALL, "");
Configuration * cfg = Configuration::create();
try {
  ... // invoke operations on cfg
} catch(const ConfigurationException & ex) {
  cout << ex.c_str() << endl;
}
cfg->destroy();

Most of the operations defined in the Configuration class can throw ConfigurationException exceptions, so those operations should be called from inside a try-catch clause. However, the create() and destroy() operations do not throw that exception, so, as shown in Figure 2.3, they can be called from outside a try-catch clause.

2.3  Utility Operations

Config4Cpp provides several utility operations, shown in Figure 2.4, that you may need to use from time to time.

Figure 2.4: Utility operations
class Configuration {
public:
    static void mergeNames(const char *    scope,
                           const char *    localName,
                           StringBuffer &  fullyScopedName);
    static bool patternMatch(const char * str,
                             const char * pattern);
    static int mbstrlen(const char * str);
    ...
};

As I discussed in Section 1.6, many Config4* operations take a pair of parameters, scope and localName, that, when merged, specify the fully-scoped name of an entry in a Configuration object. The mergeNames() operation performs that merging, and it puts the result into the fullyScopedName parameter. The fully-scoped name is usually of the form scope.localName, but if either scope or localName is an empty string, then the dot (".") is omitted when performing the merge.

The patternMatch() operation compares a string against a pattern, and returns true if they match. Within the pattern, the "*" character acts as a wildcard that matches zero or more characters. For example:

Configuration::patternMatch("Hello, world", "Hello*") → true
Configuration::patternMatch("Hello, world", "123*89") → false

Your locale setting specifies, amongst other things, the character set being used. If the locale specifies an 8-bit character set, such as ASCII or ISO-Latin-1, then each character is fully encoded in a single byte. However, if the locale specifies a multi-byte character set, such as UTF-8, then some characters may be encoded in a single byte but other characters will be encoded in a multi-byte sequence. Because of this possibility, you cannot rely on using the strlen() function to return the number of characters (instead of bytes) in a string. The mbstrlen() operation returns the number of characters in a string, regardless of the character set specified by the locale setting; it returns -1 if the string contains invalid multi-byte characters.

2.4  The parse(), fileName() and empty() Operations

Figure 2.5 shows the signatures of the fileName(), parse() and empty() operations.

Figure 2.5: The parse(), fileName() and empty() operations
class Configuration {
public:
    enum SourceType {INPUT_FILE, INPUT_STRING, INPUT_EXEC};

    const char * fileName() const;

    void parse(Configuration::SourceType    sourceType,
               const char *                 source,
               const char *                 sourceDescription = "")
                                       throw(ConfigurationException); 

    void parse(const char * sourceTypeAndSource)
                                       throw(ConfigurationException); 
    void empty();
    ...
};

The fileName() operation returns the name of the most recently parsed file. If parse() has not previously been called, then fileName() returns an empty string.

I defer discussion of the one-parameter version of parse() until Section 2.4.4 because it is just a simplified version of the three-parameter version of parse(). In the three-parameter version of parse(), the value of the first parameter determines the meaning of the other parameters.

The string returned from fileName() is used at the start of text messages inside exceptions. For example, many components of Config4Cpp (including the parser, schema validator and lookup operations) format exception messages as shown below:

if (...) {
    StringBuffer    msg;
    msg << cfg->fileName() << ": something went wrong";
    throw ConfigurationException(msg.c_str());
}

For this reason, if you call parse() with INPUT_STRING for the first parameter, then you should ensure that the value of the third parameter acts as a descriptive “file name”.

2.4.1  Parsing a File

The code segment in Figure 2.6 shows an example use of parse(). The create() operation creates a Configuration object that is empty initially. Then the parse() operation is used to populate the Configuration object. A try-catch clause is used to print any exception that might be thrown. Once the Configuration object has been populated, lookup operations (which I will discuss in Section 2.6) can be used to access information in it. Finally, destroy() is called to reclaim the memory of the Configuration object when it is no longer required.

Figure 2.6: An example of using parse()
Configuration * cfg = Configuration::create();
try {
    cfg->parse(Configuration::INPUT_FILE, "myFile.cfg");
    ... // invoke lookup operations
} catch(const ConfigurationException & ex) {
    cerr << ex.c_str() << endl;
}
cfg->destroy();

2.4.2  Parsing the Output of a Command

The example in Figure 2.6 used the following to parse a file:

cfg->parse(Configuration::INPUT_FILE, "myFile.cfg");

If, instead of parsing a file, you want to execute a command and parse its standard output, then you can do so as follows:

cfg->parse(Configuration::INPUT_EXEC, "curl -sS http://host/file.cfg");

Using INPUT_EXEC for the first parameter tells parse() to to interpret the second parameter as the name of a command to be executed.

2.4.3  Parsing a String

The example in Figure 2.6 used the following to parse a file:

cfg->parse(Configuration::INPUT_FILE, "myFile.cfg");

If, instead of parsing a file, you want to parse a string, then you can do so as follows:

const char * cfgStr = ...;
cfg->parse(Configuration::INPUT_STRING, cfgStr, "embedded configuration");

Using INPUT_STRING for the first parameter tells parse() to interpret the second parameter as configuration data that should be parsed directly, and the third parameter is the “file name” that will be used when reporting errors. You can initialise the second parameter in a variety of ways, for example:

2.4.4  The Simplified Version of parse()

The one-parameter version of parse() is a simplification wrapper around the three-parameter version. Its implementation is shown in Figure 2.7.

Figure 2.7: Simplified version of parse()
void
Configuration::parse(const char * str) throw(ConfigurationException)
{
    if (strncmp(str, "exec#", 5) == 0) {
        parse(Configuration::INPUT_EXEC, &(str[5]));
    } else if (strncmp(str, "file#", 5) == 0) {
        parse(Configuration::INPUT_FILE, &(str[5]));
    } else {
        parse(Configuration::INPUT_FILE, str);
    }
}

The following examples show how to use this simplified version:

cfg->parse("exec#curl -sS http://host/file.cfg");
cfg->parse("file#file.cfg");
cfg->parse("file.cfg");

In practice, the parameter to this operation is unlikely to be hard-coded into an application, but rather will come from, say, a command-line option or an environment variable.

2.4.5  Parsing Multiple Files and the empty() Operation

If you want to parse multiple configuration files, then you can use multiple Configuration objects. Alternatively, you can reuse the same object multiple times. If you do this, then you will probably want to call empty() between successive calls of parse(), as shown in Figure 2.8. The empty() operation has the effect of removing all variables and scopes from the Configuration object.

Figure 2.8: Calling parse() and empty() multiple times
Configuration * cfg = Configuration::create();
try {
    cfg->parse("file1.cfg");
    ... // Access the configuration information
    cfg->empty();
    cfg->parse("file2.cfg");
    ... // Access the configuration information
    cfg->empty();
    cfg->parse("file3.cfg");
    ... // Access the configuration information
    cfg->empty();
    cfg->parse("file4.cfg");
    ... // Access the configuration information
} catch(const ConfigurationException & ex) {
    cerr << ex.c_str() << endl;
}
cfg->destroy();

It is legal to call parse() multiple times without calling empty() between successive calls. If you do this, then each call to parse() merges its information with information already in the Configuration object. The Config4* parser implements the @include statement by (recursively) calling parse(), so you can think of multiple calls to parse() without calls to empty() as being similar to multiple @include statements.

It is difficult to think of a compelling reason why you might want to use a single Configuration object to parse multiple configuration files without calling empty() between successive calls of parse(). However, it is useful to know what the semantics of doing so are, because it can help you understand what is happening if you forget to call empty() between calls to parse() on the same Configuration object.

2.5  Insertion and Removal Operations

Most applications will populate a Configuration object by parsing a configuration file. However, it is possible to populate a Configuration object by using the operations shown in Figure 2.9.

Figure 2.9: Insertion and removal operations
class Configuration {
public:
    void insertString(
                    const char *            scope,
                    const char *            localName,
                    const char *            strValue)
                                        throw(ConfigurationException);
    void insertList(
                    const char *            scope,
                    const char *            localName,
                    const char **           array,
                    int                     arraySize)
                                        throw(ConfigurationException);
    void insertList(
                    const char *            scope,
                    const char *            localName,
                    const char **           nullTerminatedArray)
                                        throw(ConfigurationException);
    void insertList(
                    const char *            scope,
                    const char *            localName,
                    const StringVector &    vec)
                                        throw(ConfigurationException);
    void ensureScopeExists(
                    const char *        scope,
                    const char *        localName)
                                        throw(ConfigurationException);

    void remove(const char * scope, const char * localName)
                                        throw(ConfigurationException);
    ...
};

The insertString() operation inserts into the Configuration object an entry using the fully-scoped name (obtained by merging the scope and localName parameters) and the specified strValue. If a variable of the same name already exists in the Configuration object, then it is replaced with the new value.

The insertList() operation inserts into the Configuration object an entry using the fully-scoped name (obtained by merging the scope and localName parameters) and the specified list. If a variable of the same name already exists in the Configuration object then it is replaced with the new value. The insertList() operation is overloaded so you can specify the list in one of three different ways: as an array of strings plus the size of the array, as an array of strings terminated by a null pointer, or as a StringVector.

The insertString() and insertList() operations make a deep copy of the value when inserting it into the Configuration object.

The ensureScopeExists() operation merges the scope and localName parameters to obtain a fully-scoped name. It ensures that a scope with this fully-scoped name exists. If any ancestors of the specified scopes are missing, then they are also created. Internally, the insertString() and insertList() operations call ensureScopeExists(). Because of this, applications rarely need to call ensureScopeExists() directly.

The remove() operation merges scope and localName to form a fully-scoped name. It then removes the entry with the specified name.

If you are making use of identifiers that have "uid-" prefixes, then it is your duty to expand such identifiers before invoking any of the operations listed in Figure 2.9. Section 2.12 explains how to expand identifiers that have "uid-" prefixes.

2.6  The lookup<Type>() Operations

Figure 2.10 lists lookup-style operations that you can use to access the values of configuration variables. There are a lot of lookup operations, for the following reasons.

Figure 2.10: The lookup<Type>() operations
struct EnumNameAndValue {
    const char  *    name;
    int              value;
};

class Configuration
{
public:
    const char * lookupString(
                    const char *            scope,
                    const char *            localName,
                    const char *            defaultVal) const
                                        throw(ConfigurationException);

    const char * lookupString(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);

    void lookupList(
                    const char *            scope,
                    const char *            localName,
                    const char **&          array,
                    int &                   arraySize,
                    const char **           defaultArray,
                    int                     defaultArraySize) const
                                        throw(ConfigurationException);
    void lookupList(
                    const char *            scope,
                    const char *            localName,
                    const char **&          array,
                    int &                   arraySize) const
                                        throw(ConfigurationException);
    void lookupList(
                    const char *            scope,
                    const char *            localName,
                    StringVector &          list,
                    const StringVector &    defaultList) const
                                        throw(ConfigurationException);

    void lookupList(
                    const char *            scope,
                    const char *            localName,
                    StringVector &          list) const
                                        throw(ConfigurationException);
    int lookupInt(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupInt(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    float lookupFloat(
                    const char *            scope,
                    const char *            localName,
                    float                   defaultVal) const
                                        throw(ConfigurationException);
    float lookupFloat(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    int lookupEnum(
                    const char *                scope,
                    const char *                localName,
                    const char *                typeName,
                    const EnumNameAndValue *    enumInfo,
                    int                         numEnums,
                    const char *                defaultVal) const
                                        throw(ConfigurationException);
    int lookupEnum(
                    const char *                scope,
                    const char *                localName,
                    const char *                typeName,
                    const EnumNameAndValue *    enumInfo,
                    int                         numEnums,
                    int                         defaultVal) const
                                        throw(ConfigurationException);

    int lookupEnum(
                    const char *                scope,
                    const char *                localName,
                    const char *                typeName,
                    const EnumNameAndValue *    enumInfo,
                    int                         numEnums) const
                                        throw(ConfigurationException);

    bool lookupBoolean(
                    const char *            scope,
                    const char *            localName,
                    bool                    defaultVal) const
                                        throw(ConfigurationException);

    bool lookupBoolean(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);

    void lookupFloatWithUnits(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    float &                 floatResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);

    void lookupFloatWithUnits(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    float &                 floatResult,
                    const char *&           unitsResult,
                    float                   defaultFloat,
                    const char *            defaultUnits) const
                                        throw(ConfigurationException);

    void lookupUnitsWithFloat(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    float &                 floatResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);
    void lookupUnitsWithFloat(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    float &                 floatResult,
                    const char *&           unitsResult,
                    float                   defaultFloat,
                    const char *            defaultUnits) const
                                        throw(ConfigurationException);
    void lookupIntWithUnits(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    int &                   intResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);
    void lookupIntWithUnits(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    int &                   intResult,
                    const char *&           unitsResult,
                    int                     defaultInt,
                    const char *            defaultUnits) const
                                        throw(ConfigurationException);

    void lookupUnitsWithInt(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    int &                   intResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);
    void lookupUnitsWithInt(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    int &                   intResult,
                    const char *&           unitsResult,
                    int                     defaultInt,
                    const char *            defaultUnits) const
                                        throw(ConfigurationException);
    int lookupDurationMicroseconds(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupDurationMicroseconds(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    int lookupDurationMilliseconds(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupDurationMilliseconds(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);


    int lookupDurationSeconds(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupDurationSeconds(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    int lookupMemorySizeBytes(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupMemorySizeBytes(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    int lookupMemorySizeKB(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupMemorySizeKB(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    int lookupMemorySizeMB(
                    const char *            scope,
                    const char *            localName,
                    int                     defaultVal) const
                                        throw(ConfigurationException);
    int lookupMemorySizeMB(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
    void lookupScope(
                    const char *            scope,
                    const char *            localName) const
                                        throw(ConfigurationException);
};

The lookup operations perform error checking. For example, if the lookupInt() operation cannot convert the string value into an integer, then it throws an exception that contains an easy-to-understand error message. Likewise, if you do not specify a default value to a lookup operation and the specified configuration variable is missing (from both the main configuration object and fallback configuration), then an exception is thrown.

2.6.1  Lookup Operations for Enumerated Types

Among other parameters, the lookupEnum() operation takes an array of EnumNameAndValue structures and the size of that array. This operation calls lookupString() and then uses information in the array to convert the string value into an integer. For example:

EnumNameAndValue colourInfo[] = {
    { "red",   0 },
    { "green", 1 },
    { "blue",  2 },
};
colour = cfg->lookupEnum(scope, "font_colour", "colour", colourInfo, 3);

The typeName parameter ("colour" in the above example) specifies the “type name” of the enum names, and is used to construct an informative error message if an exception is thrown.

2.6.2  Lookup Operations for Unit-based Types

Lookup operations that have Units in their name take, among other parameters, an array of strings (specifying the allowed units) and the size of that array. An example can be seen in Figure 2.11.

Figure 2.11: Example invocation of lookupUnitsWithFloat
float         amount;
const char *  currency;
const char *  currencies[] = {"£", "$", "€"};
cfg->lookupUnitsWithFloat(scope, "discount_price", "price",
                          currencies, 3, amount, currency);

The typeName parameter ("price" in the example) specifies the “type name” of correctly-formatted strings, and is used to construct an informative error message if an exception is thrown.

The output parameters, amount and currency, contain the quantity and units that were parsed from the value of the configuration variable.

2.7  The type() and is<Type>() Operations

Figure 2.12 shows the operations you can use to query type information.

Figure 2.12: The type() and is<Type>() operations
struct EnumNameAndValue { const char * name;  int value; };
class Configuration {
public:
    enum Type {CFG_NO_VALUE       = 0, // bit masks
               CFG_STRING         = 1, // 0001
               CFG_LIST           = 2, // 0010
               CFG_SCOPE          = 4, // 0100
               CFG_VARIABLES      = 3, // 0011 = STRING | LIST
               CFG_SCOPE_AND_VARS = 7  // 0111 = STRING | LIST | SCOPE
    };
    Type type(const char * scope, const char * localName) const;
    bool isBoolean(const char * str) const;
    bool isInt(const char * str) const;
    bool isFloat(const char * str) const;
    bool isDurationMicroseconds(const char * str) const;
    bool isDurationMilliseconds(const char * str) const;
    bool isDurationSeconds(const char * str) const;
    bool isMemorySizeBytes(const char * str) const;
    bool isMemorySizeKB(const char * str) const;
    bool isMemorySizeMB(const char * str) const;
    bool isEnum(const char *                str,
                const EnumNameAndValue *    enumInfo,
                int                         numEnums) const;
    bool isFloatWithUnits(
                    const char *        str,
                    const char **       allowedUnits,
                    int                 allowedUnitsSize) const;
    bool isIntWithUnits(
                    const char *        str,
                    const char **       allowedUnits,
                    int                 allowedUnitsSize) const;
    bool isUnitsWithFloat(
                    const char *        str,
                    const char **       allowedUnits,
                    int                 allowedUnitsSize) const;
    bool isUnitsWithInt(
                    const char *        str,
                    const char **       allowedUnits,
                    int                 allowedUnitsSize) const;
    ...
};

The type() operation merges the scope and localName parameters to form the fully-scoped name of an entry in the Configuration object, and then returns the type of that entry. The return value of this operation will be one of the following:

Return value Meaning
Configuration::CFG_NO_VALUE The entry does not exist
Configuration::CFG_STRING The entry is a string variable
Configuration::CFG_LIST The entry is a list variable
Configuration::CFG_SCOPE The entry is a scope

Operations with names of the form is<Type>() return true if the str parameter is of the specified type. For example:

cfg->isBoolean("true")                     → true
cfg->isBoolean("Fred")                     → false
cfg->isDurationSeconds("2.5 minutes")      → true
cfg->isDurationSeconds("100 milliseconds") → false

The isEnum() operation takes three parameters: a string to be tested, an array of EnumNameAndValue structures and the size of that array. For example:

EnumNameAndValue colourInfo[] = {
    { "red",   0 },
    { "green", 1 },
    { "blue",  2 },
};
cfg->isEnum("red", colourInfo, 3) → true
cfg->isEnum("foo", colourInfo, 3) → false

The is<Type>() operations with "Units" in their name take three parameters: a string to be tested, an array of strings (specifying the allowed units) and the size of that array. For example:

const char * currencies[] = {"£", "$", "€"};
cfg->isUnitsWithFloat("£19.99", currencies, 3) → true
cfg->isUnitsWithFloat("foobar", currencies, 3) → false

2.8  The stringTo<Type>() Operations

Figure 2.13 lists operations that can convert a string value to another type.

Figure 2.13: The stringTo<Type>() operations
class Configuration {
public:
    bool stringToBoolean(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);
    int stringToInt(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);
    float stringToFloat(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);

    int stringToDurationSeconds(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);
    int stringToDurationMilliseconds(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);
    int stringToDurationMicroseconds(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);

    int stringToMemorySizeBytes(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);

    int stringToMemorySizeKB(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);
    int stringToMemorySizeMB(
                    const char *            scope,
                    const char *            localName,
                    const char *            str) const
                                        throw(ConfigurationException);

    int stringToEnum(
                    const char *                scope,
                    const char *                localName,
                    const char *                typeName,
                    const char *                str,
                    const EnumNameAndValue *    enumInfo,
                    int                         numEnums) const
                                        throw(ConfigurationException);

    void stringToFloatWithUnits(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char *            str,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    float &                 floatResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);
    void stringToUnitsWithFloat(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char *            str,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    float &                 floatResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);

    void stringToIntWithUnits(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char *            str,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    int &                   intResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);
    void stringToUnitsWithInt(
                    const char *            scope,
                    const char *            localName,
                    const char *            typeName,
                    const char *            str,
                    const char **           allowedUnits,
                    int                     allowedUnitsSize,
                    int &                   intResult,
                    const char *&           unitsResult) const
                                        throw(ConfigurationException);
    ...
};

An operation with a name of the form stringTo<Type>() converts a string into the specified type. If the conversion fails, then the operation throws an exception containing an informative error message. The error message will indicate that the problem arose with the variable identified by the fully-scoped name (obtained by merging the scope and localName parameters) in the fileName() configuration file.

As an example, consider a call to stringToInt() in which the scope parameter is "foo", the localName parameter is "my_list[3]" and the str parameter is "Hello, world". If the configuration file previously parsed was called example.cfg, then the message in the exception will be:

example.cfg: Non-integer value for ’foo.my_list[3]’

The intention is that developers will iterate over all the strings within a list and handcraft the localName parameter for each list element to reflect its position within the list: "my_list[1]", "my_list[2]", "my_list[3]" and so on. In this way, the stringTo<Type>() operations can produce informative exception messages if a data-type conversion fails. Note that although many programming languages, including C++, index arrays starting from 0, you should format the localName parameter so the index starts at 1. This is to be consistent with the error messages produced by the SchemaValidator class.

2.9  The List Operations

Figure 2.14 shows the operations for listing the names of entries within a scope. There are two list-type operations: listFullyScopedNames() and listLocallyScopedNames(). However, there are three overloaded versions of each operation, thus making for six variants in total.

Figure 2.14: The list operations
class Configuration {
public:
    enum Type {CFG_NO_VALUE       = 0, // bit masks
               CFG_STRING         = 1, // 0001
               CFG_LIST           = 2, // 0010
               CFG_SCOPE          = 4, // 0100
               CFG_VARIABLES      = 3, // 0011 = STRING | LIST
               CFG_SCOPE_AND_VARS = 7  // 0111 = STRING | LIST | SCOPE
    };

    void listFullyScopedNames(
                    const char *            scope,
                    const char *            localName,
                    Type                    typeMask,
                    bool                    recursive,
                    StringVector &          names) const
                                        throw(ConfigurationException);
    void listFullyScopedNames(
                    const char *            scope,
                    const char *            localName,
                    Type                    typeMask,
                    bool                    recursive,
                    const char *            filterPattern,
                    StringVector &          names) const
                                        throw(ConfigurationException);
    void listFullyScopedNames(
                    const char *            scope,
                    const char *            localName,
                    Type                    typeMask,
                    bool                    recursive,
                    const StringVector &    filterPatterns,
                    StringVector &          names) const
                                        throw(ConfigurationException);
    void listLocallyScopedNames(
                    const char *            scope,
                    const char *            localName,
                    Type                    typeMask,
                    bool                    recursive,
                    StringVector &          names) const
                                        throw(ConfigurationException);

    void listLocallyScopedNames(
                    const char *            scope,
                    const char *            localName,
                    Type                    typeMask,
                    bool                    recursive,
                    const char *            filterPattern,
                    StringVector &          names) const
                                        throw(ConfigurationException);
    void listLocallyScopedNames(
                    const char *            scope,
                    const char *            localName,
                    Type                    typeMask,
                    bool                    recursive,
                    const StringVector &    filterPatterns,
                    StringVector &          names) const
                                        throw(ConfigurationException);
    ...
};

The list operations merge the scope and localName parameters to form the fully-scoped name of a scope, and populate the output names parameter with a sorted list of the names of entries in that scope. The boolean recursive parameter specifies whether the list operation should recurse into nested sub-scopes. The typeMask parameter is a bit mask that specifies which types of entries should be listed. For example, specifying CFG_VARIABLES will list only the names of variables, while CFG_SCOPE will list only the names of scopes.

By default, a list operation lists the names of all the specified entries. However, if one or more filter patterns are specified, then the list operation will use the patternMatch() operation (Section 2.3) to compare each name against the specified patterns, and only names that match at least one filter pattern will be included in the list results.

As an example of the list functions, consider the configuration file shown below:

foo {
    timeout = "2 minutes";
    log {
        level = "2";
        file = "/tmp/foo.log";
    };
}

The following invocation of listFullyScopedNames():

cfg->listFullyScopedNames("foo", "", Configuration::CFG_SCOPE_AND_VARS,
                          true, names);

results in names containing the following strings:

"foo.log"
"foo.log.level"
"foo.log.file"
"foo.timeout"

If the same parameters are passed to listLocallyScopedNames(), then names will contain similar strings, but each string will be missing the "foo." prefix.

If you intend to make use of filter patterns, then you should note that filter patterns are matched against the strings that are produced by the list operation. For example, the filter pattern "time*" matches against "timeout", which is produced by listLocallyScopedNames() in the previous example, but it does not match against "foo.timeout", which is produced by listFullyScopedNames(). Because of this, you should use mergeNames() (Section 2.3) to prefix filter patterns with the name of the scope being listed when using listFullyScopedNames(). This is illustrated in the following example:

StringBuffer    filterPattern;
Configuration::mergeNames(scope, "time*", filterPattern);
cfg->listFullyScopedNames(scope, "", Configuration::CFG_SCOPE_AND_VARS,
                          true, filterPattern, names);

The list operations call unexpandUid()—discussed in Section 2.12—on a name before comparing it against filter patterns. Because of this, filter patterns can work with names that have an "uid-" prefix. For example, the code below obtains a list of the names of all uid-recipe scopes:

StringBuffer    filterPattern;
Configuration::mergeNames(scope, "uid-recipe", filterPattern);
cfg->listFullyScopedNames(scope, "", Configuration::CFG_SCOPE,
                          true, filterPattern, names);

2.10  Operations for Fallback Configuration

The operations for getting and setting fallback configuration are shown in Figure 2.15.

Figure 2.15: Operations for Fallback Configuration
class Configuration {
public:
    void setFallbackConfiguration(Configuration * cfg);
    void setFallbackConfiguration(
                     Configuration::SourceType sourceType,
                     const char *              source,
                     const char *              sourceDescription = "")
                                        throw(ConfigurationException);
    const Configuration * getFallbackConfiguration();
    ...
};

The one-parameter version of setFallbackConfiguration() requires you to create and populate the fallback configuration object yourself. The three-parameter version creates an initially empty fallback configuration object and then populates it by calling parse() with the specified parameters.

A “main” configuration object takes ownership of its fallback configuration object. Because of this, when you invoke destroy() on the “main” configuration object, its fallback configuration object is also destroyed.

2.11  Operations for Security Configuration

As explained in the Config4* Security chapter of the Config4* Getting Started Guide, Config4* has a built-in security policy that is applied to all Configuration objects by default. You can query the current security policy of a Configuration object by calling getSecurityConfiguration(), which is shown in Figure 2.16.

Figure 2.16: Operations for Security Configuration
class Configuration {
public:
    void setSecurityConfiguration(
                     Configuration *        cfg,
                     bool                   takeOwnership,
                     const char *           scope = "")
                                        throw(ConfigurationException);
    void setSecurityConfiguration(
                     const char *           cfgInput,
                     const char *           scope = "")
                                        throw(ConfigurationException); 
    void getSecurityConfiguration(
                     const Configuration *& cfg,
                     const char *&          scope);
    ...
};

You can change the security policy of an individual Configuration object by calling setSecurityConfiguration(). This operation is overloaded. The first variant shown in Figure 2.16 enables you to use an existing Configuration object as a security policy. If takeOwnership is true, then the target Configuration object will “take ownership” of the security policy object, which means the security policy object will be destroyed when the target Configuration object is destroyed.

The second variant of setSecurityConfiguration enables you to specify a file or "exec#..." that should be parsed to obtain a security policy. The target Configuration object will “take ownership” of the security policy object.

The security policy is specified by the combination of three variables (allow_patterns, deny_patterns and trusted_directories) in the specified scope of the security policy object. See the Config4* Security chapter of the Config4* Getting Started Guide for details.

2.12  Operations for the "uid-" Prefix

An identifier that has an "uid-" prefix has both an expanded and unexpanded form. For example, uid-000000042-recipe is an identifier in its expanded form, while uid-recipe is its unexpanded counterpart. Figure 2.17 lists the operations that Config4Cpp provides for manipulating expanded and unexpanded uid identifiers.

Figure 2.17: Operations for the "uid-" prefix
class Configuration {
public:
    void expandUid(StringBuffer & spelling)
                                        throw(ConfigurationException);
    const char * unexpandUid(
                    const char *            spelling,
                    StringBuffer &          buf) const;
    bool uidEquals(const char * s1, const char * s2) const;
    ...
};

Each Configuration object keeps an internal counter that starts at 0 and is incremented every time expandUid() encounters an "uid-" prefix. The current value of that counter is used by expandUid() to replace an identifier with its expanded form.

If you populate a Configuration object by calling parse(), then you are unlikely to need to call expandUid(), because the parser invokes that operation automatically whenever it encounters an identifier with an "uid-" prefix.

However, if you populate a Configuration object by invoking the insertion operations discussed in Section 2.5, then it is your responsibility to expand identifiers before invoking the insertion operations.

The uidEquals() operation calls unexpandUid() for both of its parameters, and tests the resulting names for equality. For example:

cfg->uidEquals("uid-000000042-recipe", "uid-recipe")   → true
cfg->uidEquals("uid-000000042-recipe", "uid-employee") → false

2.13  The dump() Operation

When Config4Cpp parses a configuration file, it stores information about scopes and name=value pairs in hash tables. Config4Cpp provides a dump() operation, shown in Figure 2.18, that converts information in the hash tables into the syntax of a Config4* file; this result is stored in the buf parameter.

Figure 2.18: The dump() operation
class Configuration {
public:
    void dump(StringBuffer &          buf,
              bool                    wantExpandedUidNames,
              const char *            scope,
              const char *            localName) const
                                        throw(ConfigurationException);
    void dump(StringBuffer & buf, bool wantExpandedUidNames) const;
    ...
};

The dump() operation is overloaded. The version that takes scope and localName parameters merges those parameters to form the fully-scoped name of an entry, and then provides a dump of that entry. This version of dump() will throw an exception if the fully-scoped name is of an non-existent entry.

The other version of the operation dumps the entire Configuration object.

Both versions of the dump() operation take a boolean parameter, wantExpandedUidNames, that specifies whether entries that have an "uid-" prefix should have their names dumped in expanded or unexpanded form.


Previous Up Next