Tag Definitions

When using XJConf you may define, how your tags will be interpreted using the so called tag definitions. These tag definition are created using XML and can be parsed with the DefinitionParser.

Defining tags

To define a new tag, all that's needed is the following XML snippet:

<defines>
  <tag name="myTag" type="java.lang.String"/>
</defines>

This creates a new tag definition for the tag <myTag/> which will then be treated as a String object.

You may now use this tag in your XML documents:

<configuration>
  <myTag>This is some text...</myTag>
</configuration>

To parse this file according to the defined rule the following code is sufficient:

import net.schst.XJConf.*;

DefinitionParser tagParser = new DefinitionParser();
NamespaceDefinitions defs = tagParser.parse("xml/defines.xml");
XmlReader conf = new XmlReader();
conf.setTagDefinitions(defs);

conf.parse("xml/conf.xml");
String mytext = (String)conf.getConfigValue("myTag");

Custom objects

In the tag definitions you can use any object you like, just supply the full qualified class name to the type attribute. There's only one simple rule that you need to follow: If you want to capture and process the cdata contents of the tag implement a constructor in the class that takes a String as its sole argument.

Adding attributes

You can also use XJConf to create more complex objects that require setter methods to be called in order to properly configure an object. To specify properties of an object you can either use attributes or nested tags.

If you decide to use attributes, you need to add them to your tag definition, let's say, you have this class:

package net.schst.xjconf.examples;

class User {
    private String name;
    private String email;
    private Boolean blocked = new Boolean(false);

    public void setName(String name) {
        this.name = name;
    }

    public void setEmail(String email) {
        this.email = email;
    }

    public void setBlocked(Boolean blocked) {
        this.blocked = blocked;
    }
}

And you want to be able to create a user object from this XML snippet:

<configuration>
  <user name="schst" email="schst@no-spam.com" blocked="true"/>
</configuration>

This can be easily done using XJConf by creating a new tag definition for the <user/> tag:

<defines>
    <tag name="user" type="net.schst.xjconf.examples.User">
        <attribute name="name" type="java.lang.String"/>
        <attribute name="email" type="java.lang.String"/>
        <attribute name="blocked" type="java.lang.Boolean"/>
    </tag>
</defines>

XJConf will automatically call the matching setter methods after converting the attribute values to the specified types.

To fetch the object, the following code is sufficient:

import net.schst.XJConf.*;

DefinitionParser tagParser = new DefinitionParser();
NamespaceDefinitions defs = tagParser.parse("xml/defines-user.xml");
XmlReader conf = new XmlReader();
conf.setTagDefinitions(defs);

conf.parse("xml/user.xml");
User myUser = (User)conf.getConfigValue("user");

Changing the setter method

It is also possible to change the attribute names without having to change the method names of the setter methods as you can specify a setter method for each attribute:

<defines>
    <tag name="refactored-user" type="net.schst.xjconf.examples.User">
        <attribute name="realname" type="java.lang.String" setter="setName"/>
        <attribute name="email" type="java.lang.String"/>
        <attribute name="nologin" type="java.lang.Boolean" setter="setBlocked"/>
    </tag>
</defines>

With this configuration, XJConf is able to parse the following XML document:

<configuration>
  <refactored-user realname="schst" email="schst@no-spam.com" nologin="false"/>
</configuration>

Requiring attributes

It is also possible to specify that an attribute must be set in tag. Just set the required attribute of the attribute tag to the value true:

<defines>
    <tag name="color" type="net.schst.XJConf.Examples.Color" keyAttribute="id">
        <attribute name="red" type="java.lang.Integer" required="true"/>
        <attribute name="green" type="java.lang.Integer" required="true"/>
        <attribute name="blue" type="java.lang.Integer" required="true"/>
    </tag>
</defines>

If you now try to parse a document using these definitions that contains a <color/> tag that has not set all of the specified values, XJConf will throw a MissingAttributeException.

Specifying default values

Needs to be written...

Nesting tags

In most cases you can choose freely whether you want to specify object properties as attributes or nested tags. So it is also possible to change the XML layout of the above example to use nested tags instead of attributes:

<defines>
  <tag name="user" type="net.schst.xjconf.examples.User"/>
  <tag name="name" type="java.lang.String"/>
  <tag name="email" type="java.lang.String"/>
  <tag name="blocked" type="java.lang.Boolean"/>
</defines>

Now you can parse the following XML document which will result in the creation of a new User object:

<configuration>
  <user>
    <name>schst</name>
    <email>chst@no-spam.com</email>
    <blocked>true</blocked>
  </user>
</configuration>

Using nested tags instead of attributes has the big advantage that the nested tags can again contain nested tags or attributes so you can pass more complex objects to the setter methods.

And of course you can also specify a setter method in your tag definition if you do not want to use the default method name.

Changing the key

You'll probably want to create more than one user from you XML document, but currently every created User object would be stored with the key user as this is the tag name. Of course you could create another tag definition with a different tag name, that maps to the same class.

As this is not very elegant, there is an easier solution:

<defines>
    <tag name="user" type="net.schst.xjconf.examples.User" keyAttribute="name">
        <attribute name="name" type="java.lang.String"/>
        <attribute name="email" type="java.lang.String"/>
        <attribute name="blocked" type="java.lang.Boolean"/>
    </tag>
</defines>

The only thing, that has been changed here is the new attribute =keyAttribute= that has been added to the tag definition. If this is set, the created object will not be stored using the tag name but it can be accessed using the value of the specified attribute. So now it is possible two create more than one user object from the same XML document:

<configuration>
  <user name="schst" email="schst@no-spam.com" blocked="true"/>
  <user name="argh" email="argh@no-spam.com" blocked="false"/>
</configuration>

And this Java code:

import net.schst.XJConf.*;

DefinitionParser tagParser = new DefinitionParser();
NamespaceDefinitions defs = tagParser.parse("xml/defines-user.xml");
XmlReader conf = new XmlReader();
conf.setTagDefinitions(defs);

conf.parse("xml/user.xml");
User schst = (User)conf.getConfigValue("schst");
User argh  = (User)conf.getConfigValue("argh");

If you use this for nested tags, they dynamically generated key will also be used to build the setter method, so in this case setSchst() and setArgh() would have been used.

If you want to change the key to a static value, this is also possible:

<defines>
    <tag name="user" type="net.schst.xjconf.examples.User" key="userObject">
        <attribute name="name" type="java.lang.String"/>
        <attribute name="email" type="java.lang.String"/>
        <attribute name="blocked" type="java.lang.Boolean"/>
    </tag>
</defines>

Now the contents of the tag user can be accessed using the key userObject.