Using tXML

Following demonstrates how to use tXML.

Configuring with tXML

Reading and parsing configuration info

tXML can be used as a replacement for java.util.Properties-styled configuration files. Sure, storing tunable constants as a java Properties file is convenient and great. However, when the size of configuration file becomes large, you will start thinking of switching to something that allows structuring of the configuration data. tXML can do this!

Another reason for using tXML as a format for configuration data is that it processes arrays and lists of items in a natural way. (In contrast, java.util.Properties is very inconvenient for storing arrays).

So, lets look at some examples to demonstrate the tXML in action.

Let our imaginary Tetris application have the following configuration info (stored in tetris.conf file):

	<tetris>
		<color>
			<background>0xD0D0D0</background>
			<foreground>0x00FF00</foreground>
		</color>
		<font>
			<title>sanserif-bolditalic-48</title>
			<main>serif-14</main>
		</font>
	</tetris>

Java code to get some of the info from configuration:

	...

	// parse tXML file with conf info
	XMLNode config = XMLParser.parse(new FileInputStream("tetris.conf"));

	...

	// get background color
	String bgcolor = config.select("//tetris/color/background").getNode(0).getValue();

	// get title font
	String titleFont = config.select("//tetris/font/title").getNode(0).getValue();

Of course, in real life the code would better check for configuration errors (like missing configuration section, or missing value). But you've got the general idea.

A utility class XMLUtil helps in selecting the values. The above example is more conveniently (and with less chance of runtime null-pointer exception) presented as:

	...

	// parse tXML file with conf info
	XMLNode config = XMLParser.parse(new FileInputStream("tetris.conf"));

	...

	// get background color
	String bgcolor = XMLUtil.selectOneValue(config, "//tetris/color/background");

	// get title font
	String titleFont = XMLUtil.selectOneValue(config, "//tetris/font/title");

Note in the examples above I used "global search" path prefix (double slash, '//') to find sequence of the requested nodes regardless of the level in XML document. This is good practice, because allows more flexibility in the configuration file structure. For example, one day you'll want to wrap configuraion data in <games> container. This will change the absolute position of the node in the document tree. However, there will be no need to re-compile the code, because global search will find it! Another reason for starting your search paths with double slash is that the configuration tree your code received may in fact be the result of some select operation made by the caller. In this case the result will be wrapped in <select> container.

Generating and storing the configuration

What if your program changed its configuration parameters? You have to generate XML tree with the new data, and write it out to the file. Like this:

	...

	// this code generates "color" part of tetris config
	XMLNode reportConfig() {
		XMLNode color = new XMLNode("color");

		color.addNode(new XMLNode("foreground", "" + fgcolor.getRGB()));
		color.addNode(new XMLNode("background", "" + bgcolor.getRGB()));

		return color;
		}


	...
	// this code writes generated config tree into "tetris.conf"
	void writeConfig() throws IOExeption {
		XMLNode cfg = tetris.reportConfig(); // this will return tetris cfg

		XMLParser.print(cfg, new FileOutputStream("tetris.conf"));
		}

Processing arrays of configurable values

Imagine, that our Tetris application takes a tile image as a GIF file. We will want to have the name of this file to be a configurable item. The problem is that every tile has in fact 4 different images (one for each orientation). Thus, one tile needs four images (and four file names in config). Configuration file might look like this:

<tetris>
	<tiles>
		<shape>
			T-shaped tile
			<image>images/tile-t-0.gif</image>
			<image>images/tile-t-1.gif</image>
			<image>images/tile-t-2.gif</image>
			<image>images/tile-t-3.gif</image>
		</shape>
		<shape>
			I-shaped tile
			<image>images/tile-i-0.gif</image>
			<image>images/tile-i-1.gif</image>
			<image>images/tile-i-2.gif</image>
			<image>images/tile-i-3.gif</image>
		</shape>

		...
	</tiles>
</tetris;>
Java code that reads all shapes, and then for each shape loads its images follows:
...
XMLNode shapes = config.select("//tetris/tiles/shape");
Shape [] shape = new Shape[shapes.getNodeCount()];

for(int i = 0; i < shapes.getNodeCount(); i++)
{
	XMLNode images = (XMLNode) shapes.getNode(i);

	Image [] image = new Image[images.getNodeCount()];
	for(int j = 0; j < images.getNodeCount(); j++)
		image[j] = getImage(images.getNode(j).getValue());

	shape[i] = new Shape(image);
}
return tile;

The helper class XMLUtil makes the above task a little bit easier:

...
XMLNode shapes = config.select("//tetris/tiles/shape");
Shape [] shape = new Shape[shapes.getNodeCount()];

for(int i = 0; i < shapes.getNodeCount(); i++)
{
	String [] images = XMLUtil.selectAllValues(shapes.getNode(i), "/image");

	Image [] image = new Image[images.length];
	for(int j = 0; j < images.length; j++)
		image[j] = getImage(images[j]);

	shape[i] = new Shape(image);
}

Messaging using tXML

When one part of program wants to inform the others that something (good or bad) have happened, it sends the message to all interested parties.

Good example is Java's ActionListener and ActionEvent. The receipients implement ActionListener interface, the sender will create ActionEvent object and send it to all listeners. ActionEvent object will contain the information nesessary for the receipient to decode the event: the sender's object, String of action command (that is the message), etc.

In the ActionEvent/ActionListener example ActionEvent was the message, with content of action command (String). When writing an application that does need a custom messaging system (i.e. when Java standard stuff is insufficient or irrelevant: e.g. non-awt application that processes events), consider tXML. The reasons:

Also XMLName object is very small (just a few bytes bigger than String), that means speed and efficiency.

Let me demonstrate how it might work.

Message receipient

Message receipient (listener):

	public interface XMLMessageListener
	{
		public void receivedXMLMessage(XMLNode message, Object source);
	}

	...

	public class MyClass implements XMLMessageListener
	{
	...

		public void receivedXMLMessage(XMLNode node, Object source)
		{
			String name   = node.getName();
			String action = node.getValue();

			if(name.equals("button"))
			{
				// all button-generated stuff goes here
				if(action.equals("pressed"))
				{
					... // do something
				}
				else if(action.equals("clicked"))
				{
					... // do something
				}
			}
			else if(name.equals("network"))
			{
				if(action.equals("disconnected"))
				{
					... // do something
				}
				else if(action.equals("reconnected"))
				{
					... // do something
				}
			}
		}
	}

Message sender

The following code is for the sender

	public class XMLMessageCaster
	{
		Vector listeners = new Vector();

		public void addXMLMessageListener(XMLMessageListener xl)
		{
			listeners.addElement(xl);
		}

		public void removeXMLMessageListener(XMLMessageListener xl)
		{
			listeners.removeElement(xl);
		}

		public void castXMLMessage(XMLNode message, Object source)
		{
			for(int i = 0; i < listeners.size(); i++)
			{
			XMLMessageListener xl = (XMLMessageListener)
				listeners.elementAt(i);

			xl.receivedXMLMessage(message, source);
			}
		}
	}

	...

	public class MySender extends XMLMessageCaster
	{
	...

		public void buttonPressed() 
		{
			castXMLMessage(new XMLNode("button", "pressed"), this);
		}

		public void buttonClicked()
		{
			castXMLMessage(new XMLNode("button", "clicked"), this);
		}
	}