Fig - Generic configuration language interpreter

Skip to end of metadata
Go to start of metadata

Introduction

Many applications need configuration files. In the old days people built their own syntax and parser. Then people tried to use simple property files with Java. Now people use XML for everything, but XML is a horrible Human interface (see Humans should not have to grok XML); XML is fine for data if you don't mind the verboseness. This article describes Fig, a little config file reader that I made for my talk at the Sydney Java users group June 20, 2007. The basic goal is to allow an application to include the fig.jar and then use the Fig parser to suck in files, the result of which is a list of objects. Fig supports strings, ints, lists, and references to other configuration objects. Fig uses reflection to create instances and call setters or set fields directly (if no setter exists). One can imagine all sorts of extensions like nested objects and so on, but this basic example should get you going.

I figured this would be a nice tutorial. I'm mainly just dumping stuff here rather than really explaining in detail, but it's simple enough you can just read it.

Example

The following example creates 3 object instances: 2 Site objects and 1 Server object:

Site jguru {
        port = 80;
        answers = "www.jguru.com";
        aliases = ["jguru.com", "www.magelang.com"];
        menus = ["FAQ", "Forum", "Search"];
}

Site bea {
        answers = "bea.jguru.com";
        menus = ["FAQ", "Forum"];
}

Server {
        sites = [$jguru, $bea];
}

where $jguru refers to the instance of type Site called jguru (the first object defined).

This assumes that you have class definitions:

and

Running Fig gives the following output:

$ java RunFig jguru.fig
Site www.jguru.com:80; [jguru.com, www.magelang.com], [FAQ, Forum, Search]
Site bea.jguru.com:0; null, [FAQ, Forum]
Server@837697

The output is generated by the following snippet in RunFig.java:

Spring IOC interface

Those familiar with Spring IOC will see a similarity with Fig. Fig could easily be extended to handle all IOC functionality. For now, here is a sample ioc.fig example:

/* This demonstrates setter injection. */
com.ociweb.springdemo.Config config1 {
        color = yellow;
        number = 19;
}

/* This demonstrates setter injection of another bean. */
com.ociweb.springdemo.MyServiceImpl myService1 {
        config = $config1;
}

com.ociweb.springdemo.Car {
        make = "Honda";
        model = "Prelude";
        year = 1997;
}

The actual IOC looks like the following. If you ask me, it's pretty gross compared to the functionally equivalent Fig notation (taken from here).

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans
  PUBLIC "-//SPRING//DTD BEAN//EN"
  "http://www.springframework.org/dtd/spring-beans.dtd">

<beans>

  <!-- This demonstrates setter injection. -->
  <bean id="config1" class="com.ociweb.springdemo.Config">
    <!-- can specify value with a child element -->
    <property name="color">
      <value>yellow</value>
    </property>
    <!-- can specify value with an attribute -->
    <property name="number" value="19"/>
  </bean>

  <!-- This demonstrates setter injection of another bean. -->
  <bean id="myService1" class="com.ociweb.springdemo.MyServiceImpl">
     <property name="config" ref="config1"/>
  </bean>

  <!-- This bean doesn't need an id because it will be
       associated with another bean via autowire by type. -->
  <bean class="com.ociweb.springdemo.Car">
    <property name="make" value="Honda"/>
    <property name="model" value="Prelude"/>
    <property name="year" value="1997"/>
  </bean>

</beans>

I'm not saying you need Fig to do config files. I'm just saying that XML is an easy, but hideous way to do config files. Fig is much smaller and easier on the eyes.

The Grammar

The syntax is pretty simple as shown in the following grammar.

grammar Fig;

file : object+ ;

object
    :   qid ID? '{' assign* '}'
    ;
    
assign
    :   ID '=' expr ';'
    ;
    
expr:   STRING
    |   INT
    |   '$' ID
    |   '[' ']'
    |   '[' expr (',' expr)* ']'
    ;
    
qid :   ID ('.' ID)* ;

STRING : '"' .* '"' ;
INT :   '0'..'9'+ ;
ID  :   ('_'|'a'..'z'|'A'..'Z') ('_'|'a'..'z'|'A'..'Z'|'0'..'9')* ;
WS  :   (' '|'\n'|'\t')+ {$channel=HIDDEN;} ;
CMT :   '/*' .* '*/'     {$channel=HIDDEN;} ;

To actually create instances and set fields, you need to add actions, return values, and parameters.

grammar Fig;

@header {
import java.util.Map;
import java.util.HashMap;
import java.util.ArrayList;
}

@members {
Map instances = new HashMap();
}

file returns [List objects]
    :   {$objects = new ArrayList();}
        (object {$objects.add($object.o);})+
    ;

object returns [Object o]
    :   qid v=ID?
        {
        $o = RunFig.newInstance($qid.text);
        if ( $v!=null ) {
            instances.put($v.text, $o);
        }
        }
        '{' assign[$o]* '}'
    ;
    
assign[Object o]
    :   ID '=' expr ';' {RunFig.setObjectProperty(o,$ID.text,$expr.value);}
    ;
    
expr returns [Object value]
    :   STRING  {$value = $STRING.text;}
    |   INT  {$value = Integer.valueOf($INT.text);}
    |   '$' ID  {$value = instances.get($ID.text);}
    |   '[' ']' {$value = new ArrayList();}
    |   {ArrayList elements = new ArrayList();}
        '[' e=expr {elements.add($e.value);}
            (',' e=expr {elements.add($e.value);})*
        ']'
        {$value = elements;}
    ;
    
qid :   ID ('.' ID)*
    ;

STRING : '"' .* '"' {setText(getText().substring(1, getText().length()-1));} ;
INT :   '0'..'9'+ ;
ID  :   ('_'|'a'..'z'|'A'..'Z') ('_'|'a'..'z'|'A'..'Z'|'0'..'9')* ;
WS  :   (' '|'\n'|'\t')+ {$channel=HIDDEN;} ;
CMT :   '/*' .* '*/'     {$channel=HIDDEN;} ;

The reflection stuff is all hidden in RunFig:

All of these files are attached if you don't want to cut/paste.

Using Fig in your application

Just create a lexer attached to some source of char like ANTLRFileStream("yourconfigfile.fig") and attach the parser to it. Call the file() method (generated from rule file) and use the return value as a list of objects. Here is a helper method you can call:

Labels:
  1. Jun 30, 2007

    Hi, marvellous (smile)  Just a note: having named parameters, instead of a List return type, should be more generic.

    Suppose that:

    •     Site objects be a database accessor classes, wrapping connection params
    •     I have to configure, in my application, two differents connections to two DBs
    •     I have two different business classes, each with their own datasource and processing logic

    The goal should be therefore to assign a given Site to a given business class. Having a List do not permit such a task. You can do this:

    But in such a way I'm embedding configuration logic in the java class - which thing I don't like (smile)

     
    The configuration may contain an optional label attribute: 


     What do you think?

  2. Apr 05, 2012

    why another syntax I figured to do exactly the same in pure python

    class jguru :
        port = 80
        answers = "www.jguru.com"
        aliases = ["jguru.com", "www.magelang.com"]
        menus = ["FAQ", "Forum", "Search"]

    class bea :
        answers = "bea.jguru.com",
        menus = ["FAQ", "Forum"]

    class Server:
        sites = [jguru, bea]

  3. Apr 05, 2012

    Salut Xavier!  Well, if you need to init a java app, python ain't gonna help (wink)

    Ter