Previous Up Next

Chapter 8  Rethinking the Architecture

8.1  Introduction

It would be great if the complete list of features in Config4* had formed in my head during, say, a weekend, and then I had spent several months designing and implementing them. But, as I explained in Chapter 2, Config4* started life with minimal functionality (just assignment statements and scopes), and slowly acquired extra features over the course of almost 15 years before its first public release. Occasionally, this resulted in me discovering that the existing architecture of Config4* was not flexible enough to elegantly support a new feature that I decided to add. In some cases, I redesigned Config4* to better accommodate the new feature. In some other cases, I added the new feature with an inelegant hack.

In this chapter, I discuss some of the Config4* features that might benefit from a better design.

8.2  Parsing @if-then-@else statements

Section 6.5 discusses a bug in the parsing of @if-then-@else statements. In summary, the parser uses a simplistic algorithm to ignore a non-executed “then” or “else” branch: it discards lexical tokens until it finds the closing "}" of the branch. The problem with this algorithm is that syntax errors in a non-executed branch can go undetected, and this violates the fail fast principle [Sho04].

8.3  Location Information in Error Messages

Section 3.5.2 explains why some error messages from Config4* do not specify the line number and file name of the source of the error. It would be good to find a memory-efficient and reasonably simple way to overcome that limitation.

8.4  Uid- entries

I introduced the "uid-" prefix fairly late in the development of Config4*. For many years before I introduced this feature, I had occasionally been frustrated at the need to define unique names for similar identifiers, for example, employee_1, employee_2, employee_3, and the need to write code that could identify and process such entries in a well-defined order.

Then one day, I started to write the Comparison with other Technologies chapter of the Config4* Getting Started Guide. In that chapter, I planned to explain how Config4* was clearly superior to other configuration technologies, including Java properties, the Windows Registry and XML. However, as I was writing the section on XML, I realised that Config4* was not clearly superior. It was better than XML in several ways, but XML had one important feature that was lacking in Config4*: the ability for a file to contain several distinct entries that have the same name. This focused my attention enough for me to brainstorm on a way to add similar functionality to Config4*. The result was that I added the "uid-" prefix to the syntax of Config4*, and I enhanced the API with some new operations to support that new feature.

The "uid-" prefix provides a solution to a particular problem. The problem is certainly an important one, but I am not sure that the "uid-" prefix is the best solution to that problem. I say that for two reasons.

Despite the above concerns, I have been reasonably happy with the "uid-" prefix so far. However, perhaps there is a still-to-be-discovered mechanism that is better and more elegant than the "uid-" prefix.

8.5  Alternative Schema Validators

There are several reasons why I like the Config4* schema validator. It provides a lot of useful functionality in a relatively small amount of code. The schema language is intuitive. And the API of the SchemaValidator class is trivial to use.

Having said all that, the schema language has limitations. Perhaps somebody will be able to enhance the schema language with new features. Or perhaps somebody will develop a competing schema language for use with Config4*. If you wish to take the latter approach, then you might find the following information useful.

For the most part, the schema validator interacts with Config4* using just its public interface. The only exception to this is that the schema validator’s lexical analyser (the SchemaLex class) inherits from the LexBase class, which currently is not part of Config4*’s documented API. There is no compelling reason for LexBase to remain an undocumented implementation detail. It is that way due to laziness on my part. So, if you want to experiment with writing your own schema validator and you would like to inherit from LexBase, then you have two options.

One option is to implement your class in the same namespace/package as Config4*. I think that might be good for prototyping purposes, but it runs the risk of the namespace/package growing to an unmanageable size, especially if several people each implement their own competing schema validators.

The other option is to first refactor code and write documentation to make LexBase a first-class citizen of the documented API of Config4*, and then write a schema validator (in a new namespace/package) that makes use of it.

8.6  Drawback of an Abstract Base Class

Consider the following scenario. Fred is a software developer working for the Acme company. He plans to use Config4J in several company-internal applications. These applications require the ability to retrieve email addresses and dates from configuration files. Fred decides to write a class called, say, AcmeConfiguration, that inherits from Configuration and adds the required lookup-style operations. Once this has been done, Fred can then use the AcmeConfiguration class in the applications he wants to implement.

Unfortunately, Fred cannot implement the AcmeConfiguration class simply by inheriting from the Configuration class and adding a few lookup<Type>() operations. This is because the Configuration class contains approximately 90 abstract operations. The AcmeConfiguration subclass will have to provide an implementation for each of those. The implementation of those inherited abstract operations can be achieved through delegation, as illustrated in Figure 8.1.

Figure 8.1: Pseudocode of a Configuration subclass
package com.acme.common;
import org.config4j.Configuration;
import java.util.Date;

public class AcmeConfiguration extends Configuration
{
    private Configuration    cfg; // delegation object

    public AcmeConfiguration()
    { cfg = Configuration.create(); }

    //--------
    // Implement all the inherited operations through delegation
    //--------
    public String lookupString(String scope, String name)
    { return cfg.lookupString(scope, name); }

    public String lookupString(String scope, String name,
                              String defaultValue)
    { return cfg.lookupString(scope, name, defaultValue); }

    ... // likewise for all the other inherited operations

    //--------
    // Now add new operations
    //--------
    public Date lookupDate(String scope, String name)
    { ... }
    public Date lookupDate(String scope, String name,
                           String defaultValue)
    { ... }
    public String lookupEmailAddress(String scope, String name)
    { ... }
    public String lookupEmailAddress(String scope, String name,
                                     String defaultValue)
    { ... }

}

This approach can work, but it involves a lot of tedious delegation code, which Fred would prefer to not have to write. I can think of three options to remove that burden from Fred.

The first option is for the Config4* library to contain a class called, say, InheritableConfiguration that implements the required delegation-based infrastructure. Then Fred could implement his AcmeConfiguration class by inheriting from InheritableConfiguration and adding just his new functionality.

The second option is to recombine the hidden implementation details of the ConfigurationImpl class with the publicly visible API of the Configuration class.

Those two options seem obvious, but I decided to not implement either one; at least not yet. This is because I suspect that: (1) I am not the first person to have encountered this issue; and (2) the options may have some subtle ramifications I have not thought of. I will wait until I gain a bit more experience in this area (or other people share their experience with me) before deciding which option to implement.

The third option, which I currently recommend, is discussed near the end of the The SchemaValidator and SchemaType Classes chapter in the Config4* Java API Guide. In brief, if Fred wants to provide lookup operations for email addresses and dates, then he should also consider providing schema support for those data types. He could then define the lookup operations on the SchemaType<Type> classes.


Previous Up Next