ASTcentric logo

Case Study: Properties

This is a little case study that shows how the ASTcentric Framework can be used.

The aim of this case study is to replace a Java properties file by an AST. A Java class (PropertiesGenerator) allows to create an instance of java.util.Properties based on such an AST. There is an AST named Properties Specifications which describes the correct structure of a poperties AST. The plain AST Editor uses this specification to create valid ASTs. In the world of XML the Properties Specifications corresponds to an DTD or an XML Schema. The AST Editor corresponds to a DTD(schema)-aware XML editor.

Table of Content:

  1. An Example: Extended Properties
  2. Properties Specifications
  3. Creating Properties ASTs
  4. Printing a Properties AST as a Properties File
A plain Java properties file is simply a list of key-value pairs where both key and value are strings. An appropriated AST would be a root node with children. Each child is a key-value pair where the key would be the name of the node and the value would be the value of the node.

For the purpose of this case study such ASTs are too simple because they are simple trees and not ASGs (i.e. Abstract Syntax Graphs) with references between nodes. To make properties ASTs real ASGs we introduce the feature that the value of a key-value pair can contain a reference to another key-value pair. There are extensions of java.util.Properties which handle such properties files (e.g. Chris Mair: Enable Constant Substitution in Property Values). Such an extended properties file would for example look like this:

host = astcentric.sourceforge.net
port = 8080
baseURL = http://${host}:${port}
Note the syntactical elements as =, ${, and } which structures such text-based properties files. In addition some escaping syntax has to be defined for these elements. This is a typcial example of a domain specific language (DSL). One of the great hopes in AST-centric progamming is that it should become easier to define and mix DSLs (see Martin Fowler: Language Workbenches: The Killer-App for Domain Specific Languages?). The root node has for each key-value pair a child node again with the key as the name of the node. The value is the concatenation of the children of a key-value node. There are two types of value nodes: Plain Value nodes with a plain string value and Value Reference nodes which refer to key-value nodes in the AST. The example from the last chapter will look in the plain AST editor like



Fig. 1: AST: Properties Example

Node names are in black. Node names of referred nodes are in violet. Node values are in orange. The references Properties, Property, Plain Value, and Value Reference are all nodes from the AST Properties Specification:



Fig. 2: AST: Properties Specification

Green node names mean that these nodes can be referred by nodes from another AST. Nodes referring to these nodes define their types. For example, the node named host refers to the node named Property is a node of type Property.

All green nodes in Fig. 2 are of type Validator which is a node of the AST Validation Declaration. This AST is part of the ASTcentric distribution. It defines the current default AST Validation Language. Unfolding the green nodes reveals their definition in terms of this language which is of course another DSL. The AST Validation Language is rather complex. The is currently no good documentation because this language is still not finished.

In order to understand some of its elements all four major nodes of Properties Specification are briefly discussed. They are not discussed in order they appear in the AST but in the order from simple to complex.



Fig. 3: Node Plain Value

There are two child nodes. Each refer to a node of the AST Validation Declaration and define a condition. A node is a valid node of type Plain Value if it fulfills all conditions. They are
has value
The node to be validated should have a value. The child node defines a validator of that value. A validator validates type and value. Here the validator is a Regular Expression. This means that the value of the node to be validated has to be a string which matches the regular expression (here .*).

Remark: Regular Expression is another DSL. Using a textual representation of this language is of course inconsistent with the AST-centric apporach.

Children
The node to be validated should have children as specified by its UML-like multiplicity value. Here it is 0 which means that a node of type Plain Value should have no children. Usually a Children node appears as a child node of a Children Composition (see next section).



Fig. 4: Node Property

This specification says that a node of type Property should have a name which matches the specified regular expression and which has an arbitrary number of children which are either of type Plain Value or Value Reference.

This is expressed by the node of the type Children Composition. Such a node has one or many nodes of the type Children. The child nodes of a Children node define conditions a child of the node to be validated has to fulfill in order to be counted. The UML-like multiplicity specifies the valid range of counts. A Children Composition is valid when its sequence of Children definitions is valid.

It is typical that a Children has a child of type has reference. Again its children specify conditions which have to be fulfilled by the node referred by the node to be validated. Here it has to be a member of a set specified by the from set node. If the value of the from set node is false it is forbidden to be a member of the set.

The children of a from set node define node collections. The set is the union of these collections. Here there is only one child. It is a Node Collection node: The collection is defined by the references of its children.



Fig. 5: Node Properies

The first condition says that all names of the children of the node to be validated have to be unique. The children of type Unique Name define node collections. The condition is checked for the union of these collections. A collections of type descendent of specifies descendent nodes either of the node for which the Unique Name condition applies or for the collection specified by its second child. The first child specifies the degree of relationship.

The second condition is very similar to the second condition in Fig. 4: It says that a node of type Properties has arbitray many children. All children have to by of type Property.



Fig. 6: Node Value Reference

A node of this type has exactly one child which has a reference from the set defined by all older siblings of the Property node which is the grandparent of that child.

Two nested ancestors of nodes both of type parent are used to express a grandparent relationship. To create Properties ASTs one has to download astcentric-example-properties.jar. It contains the AST Properties Specification described in Sec. 2.

  1. Start Eclipse with the installed ASTcentric plugin. For installation see Installation and Start and Configuration.
  2. Create a new Java project.
  3. Create a folder named asts in this project.
  4. Copy astcentric-example-properties.jar into the project.
  5. Choose in the project's context menu Properties and select AST Editor Properties in the popup dialog:

  6. Choose the folder asts as the folder for editable ASTs and astcentric-example-properties.jar for the build path as in the screen shot.
Invoke the editor as follows:
java -cp astcentric-example-properties.jar:astcentric-editor.jar:astcentric.jar \ 
     astcentric.editor.swing.plain.ASTEditor
The following steps describe partially how to create the AST shown in Fig. 1. For a general introduction of editing an AST see Creating an AST. Here only the differences caused by the Properties Specification is discussed.
  1. Create a new AST (Eclipse: in folder asts).
  2. Select the root node and hit the R key for editing the reference of the root node. Type P in the popup list of possible references. This shrinks the list. Choose Properties and hit the enter key.
  3. Hit CTRL-ENTER to create a child node. It not only creates a child node. Because of the Properties Specification the editor knows that only children referring Property are valid. Therefore it automatically adds the appropriated reference. In addition a dialog pops up asking the user to enter a name for the node because a Property node should have a name in accordance with Properties Specification:

  4. After entering the name hit again CTRL-ENTER to create a child node. A new node appears with reference selection popup list containing only two possibilities the user can choose: Plain Value and Value Reference.
  5. When choosing Plain Value the reference is added and a popup is asking for a value because nodes of type Plain Value have to have a string value.
  6. When choosing Value Reference the node will be marked as invalid. Moving the mouse cursor over the node shows a tooltip as follows:

    It means that a child node is missing. Creating a child causes again a popup for choosing a reference. In this case from the set of already defined properties.
To turn a Properties AST into a java.util.Properties object needs the class PropertiesGenerator from astcentric-example-properties.jar.It is used in the following code to print a Properties AST onto the console:
  1: import java.io.*;
  2: import java.util.*;
  3: 
  4: import astcentric.example.properties.PropertiesGenerator;
  5: import astcentric.structure.basic.*;
  6: import astcentric.structure.filesystem.RealFile;
  7: 
  8: public class PropertiesASTPrinter
  9: {
 10:   public static void main(String[] args) throws Exception
 11:   {
 12:     if (args.length == 0)
 13:     {
 14:       System.out.println("Usage: java PropertiesASTPrinter <AST file>");
 15:       System.exit(1);
 16:     }
 17:     
 18:     ASTPath rootPath = new ASTPath(System.getProperty("java.class.path"));
 19:     ASTLoader rootLoader = new DefaultASTLoader(rootPath, true);
 20:     ASTPath path = new ASTPath(new RealFile(new File(args[0])));
 21:     ASTLoader loader = new DefaultASTLoader(rootLoader, path, true);
 22:     List<ASTInfo> infos = ASTTool.getAllLoadableASTs(loader, false);
 23:     AST ast = loader.loadAST(infos.get(0).getID());
 24:     Properties properties = PropertiesGenerator.generate(ast.getRoot());
 25:     ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
 26:     properties.store(outputStream, "Created from AST " + ast.getInfo());
 27:     System.out.println(new String(baos.toByteArray()));
 29:   }
 30: }
Applied to the AST of Fig. 1 would lead to
#Created from AST astcentric.sourceforge.net*fjelmer*12csd6dh1 My first Properties AST
#Sun Jun 17 16:10:28 CEST 2007
port=8080
host=astcentric.sourceforge.net
baseURL=http\://astcentric.sourceforge.net\:8080
Line 18 and 19 define the root AST loader which contains all ASTs found in the Java class path:
 18:     ASTPath rootPath = new ASTPath(System.getProperty("java.class.path"));
 19:     ASTLoader rootLoader = new DefaultASTLoader(rootPath, true);
The two following lines define an AST loader only for the AST file of the command line argument. It has rootLoader as its parent loader.
 20:     ASTPath path = new ASTPath(new RealFile(new File(args[0])));
 21:     ASTLoader loader = new DefaultASTLoader(rootLoader, path, true);
The AST Properties Specification referred by a Properties AST will be loaded by the root loader. An AST is loaded by invocating the method loadAST() of the AST loader. The argument of this method is the ID of the AST to be loaded. To get the ID the informations of all ASTs loadable by this loader is obtained. Because there is only one AST there is no doubt which entry in the list of ASTInfo instances has to be asked for the ID:
 22:     List<ASTInfo> infos = ASTTool.getAllLoadableASTs(loader, false);
 23:     AST ast = loader.loadAST(infos.get(0).getID());
Line 24 creates a Properties object from the AST by using PropertiesGenerator. The rest of the code deals with printing the Properties object on the console. AST ID and name are added as a comment. Its only public method reads
 1:  public static Properties generate(Node propertiesNode)
 2:  {
 3:    Node reference = propertiesNode.getReference();
 4:    if (reference == null)
 5:    {
 6:      throw new NodeException(propertiesNode,
 7:                              "Reference to Properties Declaration missing");
 8:    }
 9:    AST ast = reference.getAST();
10:    PropertiesDeclaration declaration = new PropertiesDeclaration(ast);
11:    Properties properties = new Properties();
12:    List propNodes = NodeTool.getNodesOf(propertiesNode);
13:    for (Node propNode : propNodes)
14:    {
15:      String name = propNode.getName();
16:      if (name == null)
17:      {
18:        throw new NodeException(propNode, "Nameless property node.");
19:      }
20:      properties.setProperty(name, getValue(propNode, declaration));
21:    }
22:    return properties;
23:  }
In line 12 all child nodes are obtained from the argument node. It follows a loop over them assuming each child is of type Property. In line 20 the method getValue() is invoked with this child node. The second argument is a helper class instanciated in line 10. It has the nodes of Property, Plain Value, and Value Reference. They are need to distinguish the different types of child nodes of a Property node (line 9, 13, 18):
 1:  private static String getValue(Node node, 
 2:                                 final PropertiesDeclaration declaration)
 3:  {
 4:    Node reference = node.getReference();
 5:    if (reference == null)
 6:    {
 7:      throw new NodeException(node, "Missing reference.");
 8:    }
 9:    if (NodeTool.same(reference, declaration.plainValueNode))
10:    {
11:      return node.getValue().toString();
12:    } 
13:    if (NodeTool.same(reference, declaration.valueReferenceNode))
14:    {
15:      Node ref = NodeTool.getNodesOf(node).get(0).getReference();
16:      return getValue(ref, declaration);
17:    }
18:    if (NodeTool.same(reference, declaration.propertyNode))
19:    {
20:      final StringBuilder builder = new StringBuilder();
21:      node.traverseNodes(new NodeHandler()
22:        {
23:          public boolean handle(Node child)
24:          {
25:            builder.append(getValue(child, declaration));
26:            return false;
27:          }
28:        });
29:      return new String(builder);
30:    }
31:    throw new NodeException(node, "Isn't declared correctly: " + reference);
32:  }
In line 25 the method calls itself recursively in order to resolve references of references. Note, that in line 21 an inner iterator is used to go over all children of a Property node.