Previous Up Next

Chapter 2  The Configuration Class

2.1  The ConfigurationException Class

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

Figure 2.1: The ConfigurationException class
package org.config4j;

public class ConfigurationException extends java.lang.RuntimeException
{
    public ConfigurationException(String msg);
}

2.2  The create() Operation

Most of the public API of Config4J is defined in the Configuration class. This is an abstract class, so you cannot create an instance of it directly. Instead, you create a Configuration object by calling the static create() operation, which is shown in Figure 2.2.

Figure 2.2: Initialization API for Configuration
package org.config4j;

public abstract class Configuration
{
    public static Configuration create();
    ...
}

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.

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() operation does not throw that exception, so, as shown in Figure 2.3, it can be called from outside a try-catch clause.

Figure 2.3: Example of creating a Configuration object
import org.config4j.Configuration;
import org.config4j.ConfigurationException;
...
Configuration * cfg = Configuration.create();
try {
    ... // invoke operations on cfg
} catch(ConfigurationException ex) {
  System.err.println(ex.getMessage());
}

2.3  Utility Operations

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

Figure 2.4: Utility operations
package org.config4j;

public abstract class Configuration
{
    public static String mergeNames(String scope, String localName);
    public static boolean patternMatch(String str, String pattern);
    public String getenv(String name);
    ...
}

As I discussed in Section 1.5, 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. 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

The getenv() operation returns the value of the specified environment variable if it exists, and null otherwise.

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
package org.config4j;

public abstract class Configuration
{
    // Constant values used for sourceType in parse()
    public static final int INPUT_FILE   = 1;
    public static final int INPUT_STRING = 2;
    public static final int INPUT_EXEC   = 3;

    public String fileName();
    public void parse(
        int          sourceType,
        String       source,
        String       sourceDescription) throws ConfigurationException;
    public void parse(
        int          sourceType,
        String       source) throws ConfigurationException;
    public void parse(String sourceTypeAndSource)
                                        throws ConfigurationException; 
    public 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.

Calling the two-parameter version of parse() is similar calling the three-parameter version with an empty string for the last parameter.

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

if (...) {
    throw ConfigurationException(cfg->fileName()
                                 + ": something went wrong");
}

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.

Figure 2.6: An example of using parse()
Configuration cfg = Configuration.create();
try {
    cfg.parse(Configuration.INPUT_FILE, "myFile.cfg");
    ... // invoke lookup operations
} catch(ConfigurationException ex) {
    System.err.println(ex.getMessage());
}

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:

String 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()
public void parse(String sourceTypeAndSource)
                                         throws ConfigurationException
{
    int       cfgSourceType;
    String    cfgSource;

    if (sourceTypeAndSource.startsWith("exec#")) {
        cfgSource = sourceTypeAndSource.substring(5);
        cfgSourceType = Configuration.INPUT_EXEC;
    } else if (sourceTypeAndSource.startsWith("file#")) {
        cfgSource = sourceTypeAndSource.substring(5);
        cfgSourceType = Configuration.INPUT_FILE;
    } else {
        cfgSource = sourceTypeAndSource;
        cfgSourceType = Configuration.INPUT_FILE;
    }
    parse(cfgSourceType, cfgSource);
}

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(ConfigurationException ex) {
    System.err.println(ex.getMessage()):
}

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
package org.config4j;

public abstract class Configuration
{
    public void insertString(
        String    scope,
        String    localName,
        String    strValue) throws ConfigurationException;
    public void insertList(
        String    scope,
        String    localName,
        String[]  listValue) throws ConfigurationException;
    public void ensureScopeExists(
        String    scope,
        String    localName) throws ConfigurationException;
    public void remove(
        String    scope,
        String    localName) throws ConfigurationException; 
    ...
}

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

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
package org.config4j;

public class EnumNameAndValue
{
    public EnumNameAndValue(String name, int value);
    public String getName();
    public int    getValue();
}

public class ValueWithUnits
{
    public ValueWithUnits();
    public ValueWithUnits(int value, String units);
    public ValueWithUnits(float value, String units);
    public int    getIntValue();
    public float  getFloatValue();
    public String getUnits();
}

public abstract class Configuration
{
    public String lookupString(
        String        scope,
        String        localName,
        String        defaultVal) throws ConfigurationException;
    public String lookupString(String scope, String localName)
                                        throws ConfigurationException; 
    public String lookupString(
        String        scope,
        String        localName,
        String        defaultVal) throws ConfigurationException;
    public String lookupString(String scope, String localName)
                                        throws ConfigurationException; 
    public String[] lookupList(
        String        scope,
        String        localName,
        String[]      defaultArray) throws ConfigurationException;
    public String[] lookupList(String scope, String localName)
                                        throws ConfigurationException; 

    public int lookupInt(
        String        scope,
        String        localName,
        int           defaultVal) throws ConfigurationException;
    public int lookupInt(String scope, String localName)
                                        throws ConfigurationException; 

    public float lookupFloat(
        String        scope,
        String        localName,
        float         defaultVal) throws ConfigurationException;
    public float lookupFloat(String scope, String localName)
                                        throws ConfigurationException; 

    public int lookupEnum(
        String              scope,
        String              localName,
        String              typeName,
        EnumNameAndValue[]  enumInfo,
        String              defaultVal) throws ConfigurationException;
    public int lookupEnum(
        String              scope,
        String              localName,
        String              typeName,
        EnumNameAndValue[]  enumInfo,
        int                 defaultVal) throws ConfigurationException;
    public int lookupEnum(
        String              scope,
        String              localName,
        String              typeName,
        EnumNameAndValue[]  enumInfo) throws ConfigurationException;

    public boolean lookupBoolean(
        String              scope,
        String              localName,
        boolean             defaultVal) throws ConfigurationException;
    public boolean lookupBoolean(
        String              scope,
        String              localName) throws ConfigurationException;

    public ValueWithUnits lookupFloatWithUnits(
        String              scope,
        String              localName,
        String              typeName,
        String[]            allowedUnits)
                                        throws ConfigurationException; 
    public ValueWithUnits lookupFloatWithUnits(
        String              scope,
        String              localName,
        String              typeName,
        String[]            allowedUnits,
        ValueWithUnits      defaultValueWithUnits)
                                        throws ConfigurationException; 

    public ValueWithUnits lookupUnitsWithFloat(
        String            scope,
        String            localName,
        String            typeName,
        String[]          allowedUnits) throws ConfigurationException;
    public ValueWithUnits lookupUnitsWithFloat(
        String            scope,
        String            localName,
        String            typeName,
        String[]          allowedUnits,
        ValueWithUnits    defaultValueWithUnits)
                                        throws ConfigurationException;

    public ValueWithUnits lookupIntWithUnits(
        String            scope,
        String            localName,
        String            typeName,
        String[]          allowedUnits) throws ConfigurationException;
    public ValueWithUnits lookupIntWithUnits(
        String            scope,
        String            localName,
        String            typeName,
        String[]          allowedUnits,
        ValueWithUnits    defaultValueWithUnits)
                                        throws ConfigurationException; 

    public ValueWithUnits lookupUnitsWithInt(
        String            scope,
        String            localName,
        String            typeName,
        String[]          allowedUnits) throws ConfigurationException;
    public ValueWithUnits lookupUnitsWithInt(
        String            scope,
        String            localName,
        String            typeName,
        String[]          allowedUnits,
        ValueWithUnits    defaultValueWithUnits)
                                        throws ConfigurationException; 
    public int lookupDurationMicroseconds(
        String            scope,
        String            localName,
        int               defaultVal) throws ConfigurationException;
    public int lookupDurationMicroseconds(
        String            scope,
        String            localName) throws ConfigurationException;
    public int lookupDurationMilliseconds(
        String            scope,
        String            localName,
        int               defaultVal) throws ConfigurationException;
    public int lookupDurationMilliseconds(
        String            scope,
        String            localName) throws ConfigurationException;
    public int lookupDurationSeconds(
        String            scope,
        String            localName,
        int               defaultVal) throws ConfigurationException;
    public int lookupDurationSeconds(
        String            scope,
        String            localName) throws ConfigurationException;
    public int lookupMemorySizeBytes(
        String            scope,
        String            localName,
        int               defaultVal) throws ConfigurationException;
    public int lookupMemorySizeBytes(
        String            scope,
        String            localName) throws ConfigurationException;

    public int lookupMemorySizeKB(
        String            scope,
        String            localName,
        int               defaultVal) throws ConfigurationException;
    public int lookupMemorySizeKB(
        String            scope,
        String            localName) throws ConfigurationException;
    public int lookupMemorySizeMB(
        String            scope,
        String            localName,
        int               defaultVal) throws ConfigurationException;
    public int lookupMemorySizeMB(
        String            scope,
        String            localName) throws ConfigurationException;
    public void lookupScope(
        String            scope,
        String            localName) throws 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 objects. This operation calls lookupString() and then uses the array to convert the string value into an integer. For example:

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

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 that specifies the allowed units. An example can be seen in Figure 2.11. 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.

Figure 2.11: Example invocation of lookupUnitsWithFloat
String currencies[] = new String[] {"£", "$", "€"};
ValueWithUnits vu = cfg.lookupUnitsWithFloat(scope, "discount_price",
                                             "price", currencies);
System.out.println("Currency = " + vu.getUnits());
System.out.println("Amount   = " + vu.getFloatValue());

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
package org.config4j;
public class EnumNameAndValue {
    public EnumNameAndValue(String name, int value);
    public String getName();
    public int    getValue();
}
public abstract class Configuration {
    // Type constants
    public static final int CFG_NO_VALUE      = 0;// bit masks
    public static final int CFG_STRING        = 1;// 0001
    public static final int CFG_LIST          = 2;// 0010
    public static final int CFG_SCOPE         = 4;// 0100
    public static final int CFG_VARIABLES     = 3;// STRING|LIST
    public static final int CFG_SCOPE_AND_VARS= 7;// STRING|LIST|SCOPE

    public int type(String scope, String localName);
    public boolean isBoolean(String str);
    public boolean isInt(String str);
    public boolean isFloat(String str);
    public boolean isDurationMicroseconds(String str);
    public boolean isDurationMilliseconds(String str);
    public boolean isDurationSeconds(String str);
    public boolean isMemorySizeBytes(String str);
    public boolean isMemorySizeKB(String str);
    public boolean isMemorySizeMB(String str);
    public boolean isEnum(
        String                str,
        EnumNameAndValue[]    enumInfo);
    public boolean isFloatWithUnits(
        String                str,
        String[]              allowedUnits);
    public boolean isIntWithUnits(
        String                str,
        String[]              allowedUnits);
    public boolean isUnitsWithFloat(
        String                str,
        String[]              allowedUnits);
    public boolean isUnitsWithInt(
        String                str,
        String[]              allowedUnits);
    ...
}

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 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 two parameters—a string to be tested and an array of EnumNameAndValue objects—as you can see in Figure 2.13.

Figure 2.13: Examples of calling isEnum()
EnumNameAndValue colourInfo[] = new EnumNameAndValue[] {
    new EnumNameAndValue("red",   0),
    new EnumNameAndValue("green", 1),
    new EnumNameAndValue("blue",  2)
};
cfg.isEnum("red", colourInfo) → true
cfg.isEnum("foo", colourInfo) → false

The is<Type>() operations with "Units" in their name take two parameters—a string to be tested and an array of strings that specifies the allowed units—as you can see in Figure 2.14.

Figure 2.14: Examples of calling isUnitsWithFloat
String currencies[] = new String[] {"£", "$", "€"};
cfg.isUnitsWithFloat("£19.99", currencies) → true
cfg.isUnitsWithFloat("foobar", currencies) → false

2.8  The stringTo<Type>() Operations

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

Figure 2.15: The stringTo<Type>() operations
package org.config4j;

public class EnumNameAndValue {
    public EnumNameAndValue(String name, int value);
    public String getName();
    public int    getValue();
}

public class ValueWithUnits {
    public ValueWithUnits();
    public ValueWithUnits(int value, String units);
    public ValueWithUnits(float value, String units);
    public int    getIntValue();
    public float  getFloatValue();
    public String getUnits();
}

public abstract class Configuration
{
    public boolean stringToBoolean(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public int stringToInt(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public float stringToFloat(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public int stringToDurationSeconds(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public int stringToDurationMilliseconds(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;

    public int stringToDurationMicroseconds(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public int stringToMemorySizeBytes(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public int stringToMemorySizeKB(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;
    public int stringToMemorySizeMB(
        String            scope,
        String            localName,
        String            str) throws ConfigurationException;

    public int stringToEnum(
        String              scope,
        String              localName,
        String              type,
        String              str,
        EnumNameAndValue[]  enumInfo) throws ConfigurationException;

    public ValueWithUnits stringToFloatWithUnits(
        String              scope,
        String              localName,
        String              typeName,
        String              str,
        String[]            allowedUnits)
                                        throws ConfigurationException; 

    public ValueWithUnits stringToUnitsWithFloat(
        String              scope,
        String              localName,
        String              typeName,
        String              str,
        String[]            allowedUnits)
                                        throws ConfigurationException; 

    public ValueWithUnits stringToIntWithUnits(
        String              scope,
        String              localName,
        String              typeName,
        String              str,
        String[]            allowedUnits)
                                        throws ConfigurationException; 
    public ValueWithUnits stringToUnitsWithInt(
        String              scope,
        String              localName,
        String              typeName,
        String              str,
        String[]            allowedUnits)
                                        throws 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 Java, 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.16 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.16: The list operations
package org.config4j;

public abstract class Configuration
{
    // Type constants
    public static final int CFG_NO_VALUE      = 0;// bit masks
    public static final int CFG_STRING        = 1;// 0001
    public static final int CFG_LIST          = 2;// 0010
    public static final int CFG_SCOPE         = 4;// 0100
    public static final int CFG_VARIABLES     = 3;// STRING|LIST
    public static final int CFG_SCOPE_AND_VARS= 7;// STRING|LIST|SCOPE

    public String[] listFullyScopedNames(
        String       scope,
        String       localName,
        int          typeMask,
        boolean      recursive) throws ConfigurationException;
    public String[] listFullyScopedNames(
        String       scope,
        String       localName,
        int          typeMask,
        boolean      recursive,
        String       filterPattern) throws ConfigurationException;
    public String[] listFullyScopedNames(
        String       scope,
        String       localName,
        int          typeMask,
        boolean      recursive,
        String []    filterPatterns) throws ConfigurationException;
    public String[] listLocallyScopedNames(
        String       scope,
        String       localName,
        int          typeMask,
        boolean      recursive) throws ConfigurationException;
    public String[] listLocallyScopedNames(
        String       scope,
        String       localName,
        int          typeMask,
        boolean      recursive,
        String       filterPattern) throws ConfigurationException;

    public String[] listLocallyScopedNames(
        String       scope,
        String       localName,
        int          typeMask,
        boolean      recursive,
        String []    filterPatterns) throws 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():

String[] names = cfg.listFullyScopedNames("foo", "",
                                Configuration.CFG_SCOPE_AND_VARS, true);

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:

String filterPattern = Configuration.mergeNames(scope, "time*");
String names = cfg.listFullyScopedNames(scope, "",
                Configuration.CFG_SCOPE_AND_VARS, true, filterPattern);

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:

String filterPattern = Configuration.mergeNames(scope, "uid-recipe");
String names = cfg.listFullyScopedNames(scope, "",
                         Configuration.CFG_SCOPE, true, filterPattern);

2.10  Operations for Fallback Configuration

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

Figure 2.17: Operations for Fallback Configuration
package org.config4j;

public abstract class Configuration
{
    public void setFallbackConfiguration(Configuration cfg);

    public void setFallbackConfiguration(
        int         sourceType,
        String      source) throws ConfigurationException;

    public void setFallbackConfiguration(
        int         sourceType,
        String      source,
        String      sourceDescription) throws ConfigurationException;

    public Configuration getFallbackConfiguration();
    ...
}

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

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—which consists of a Configuration object and a scope within it—of a Configuration object by calling getSecurityConfiguration() and getSecurityConfigurationScope(), which are shown in Figure 2.18.

Figure 2.18: Operations for Security Configuration
package org.config4j;

public abstract class Configuration
{
    public void setSecurityConfiguration(Configuration cfg)
                                        throws ConfigurationException; 
    public void setSecurityConfiguration(
        Configuration    cfg,
        String           scope) throws ConfigurationException; 
    public void setSecurityConfiguration(String cfgInput)
                                        throws ConfigurationException; 
    public void setSecurityConfiguration(
        String           cfgInput,
        String           scope) throws ConfigurationException; 

    public Configuration  getSecurityConfiguration();
    public String         getSecurityConfigurationScope();
    ...
}

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.18 uses the global scope of an existing Configuration object as a security policy.

The second variant enables you to use the specified scope of an existing Configuration object as a security policy.

The third variant of setSecurityConfiguration enables you to specify a file or "exec#..." that should be parsed, and combined with the specified scope to obtain a security policy.

The details of a security policy are specified by the allow_patterns, deny_patterns and trusted_directories variables 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.19 lists the operations that Config4J provides for manipulating expanded and unexpanded uid identifiers.

Figure 2.19: Operations for the "uid-" prefix
package org.config4j;

public abstract class Configuration
{
    public String expandUid(String str)
                                        throws ConfigurationException; 
    public String unexpandUid(String spelling)
                                        throws ConfigurationException; 
    public boolean uidEquals(String str1, String str2);
    ...
}

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 Config4J parses a configuration file, it stores information about scopes and name=value pairs in hash tables. Config4J provides a dump() operation, shown in Figure 2.20, that converts information in the hash tables into the syntax of a Config4* file.

Figure 2.20: The dump() operation
package org.config4j;

public abstract class Configuration
{
    public String dump(
         boolean    wantExpandedUidNames,
         String     scope,
         String     localName) throws ConfigurationException;
    public String dump(boolean wantExpandedUidNames);
    ...
}

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