Localized resource building with JSR 238: a simple custom ant task example

For today’s programming adventure, let’s tackle resource building!!!

Creating a series of jar files containing different sets of specialized resources is one of the basic reasons for setting up a build process with a tool such as Ant rather than just building with ktoolbar (as I discussed the other day). To put this in concrete terms, let’s look at a practical example that illustrates both how to write a simple Ant task and how to create resource files for the Mobile Internationalization API (JSR 238).

The Mobile Internationalization API provides a standard way to customize resources such as label strings and images and pack them efficiently into the jar file. As long as the resources are organized correctly, the API consults the platform behind the scenes to determine which set of resources should be used at any given moment. The trick is building the resource files and the correct resource directories. JSR 238 defines a special file format (.res) that allows you to pack a set of different resources together in a single, efficient binary file. As you might imagine, that means there’s a little work to be done to create the resource files — you can’t just create them by hand in a text editor as you might with a properties file.

Nokia has been kind enough to provide a reference implementation for this JSR that you can download here. The Nokia reference implementation provides both the tools to create the resource files and an emulator that allows you to test a MIDlet that uses JSR 238. All of these tools were written with the assumption that you’re using Windows — and the emulator won’t run on any other operating system — but the tools to build and test the resource files are pure Java, so you can use them on any system.

The Nokia reference implementation provides some complete sample programs as well to demonstrate how to call the resource building tools from within an Ant build file. You can see from looking at the MoonDemo that in fact that ResourceMaker tool can be integrated into an ant build as it is. Here’s the relevant snippet from the build.xml file:

<target name=”makeres” depends=”preverify”>
<java classname=”com.nokia.phone.ri.global.tools.ResourceMaker”
classpath=”${ri.home}/tools” >
<arg line=”src/res.xml src res MoonPhases en fi-FI da-DK hu-HU” />
</java>
</target>
Any java class can be called from within an Ant build file by using the “java” element (the “java” ant task) as seen here.

However, I wanted to get a better idea of how to write a custom Ant task, and this seemed like as good an application as any since the argument line is a little complicated and not terribly intuitive. So as an exercise, I wrote an Ant task that makes exactly the same call to the nokia ResourceMaker tool, but does it from a custom Ant task as follows:

<?xml version=”1.0″ encoding=”ISO-8859-1″?>

<!– A simple example ant task –>
<project name=”ResourceAntTask” default=”resources” basedir=”.”>

<!– define the task by binding it to the corresponding java code –>
<taskdef name=”buildres” classname=”net.frog_parrot.ant.BuildResources” classpath=”/home/carol/myAntTask/bin:/home/carol/JSR_238_RI_1_0/tools/ResourceMaker/bin/”/>
<!– define a custom type that is used by the task –>
<typedef name=”locale” classname=”net.frog_parrot.ant.StringElement” classpath=”/home/carol/myAntTask/bin”/>

<!– This build file’s one target calls the custom task –>
<target name=”resources”>
<buildres descriptor=”/home/carol/JSR_238_RI_1_0/demos/MoonDemo/src/res.xml” source=”/home/carol/JSR_238_RI_1_0/demos/MoonDemo/src/” destination=”.” name=”MoonPhases”>
<locale name=”en”/>
<locale name=”hu-HU”/>
</buildres>
</target>

</project>
The above is a complete build file, which would normally be named build.xml.

To create the ant task, I need to write the class BuildResources.java which extends org.apache.tools.ant.Task (see the Apache Ant JavaDoc for details on the various classes available).Here’s the code:

package net.frog_parrot.ant;

import java.util.Vector;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.tools.ant.types.DataType;

import com.nokia.phone.ri.global.tools.ResourceMaker;

/**
* A class to call the Nokia ResourceMaker from ant
* (to build JSR 238 resource files).
*/
public class BuildResources extends Task {

private String descriptor;
private String source;
private String destination;
private String name;
private Vector locales = new Vector();

/**
* Set the complete path to the XML file that
* describes the resource files to construct.
*/
public void setDescriptor(String descriptor) {
this.descriptor = descriptor;
}

/**
* Set the source directory containing the
* initial resource files.
*/
public void setSource(String source) {
this.source = source;
}

/**
* Set the directory to place the newly-constructed
* resources in.
*/
public void setDestination(String destination) {
this.destination = destination;
}

/**
* Set the base name for the resource file.
*/
public void setName(String name) {
this.name = name;
}

/**
* Add a locale string to the list of locales
* to build the resources for
* @param locale a locale identifier,
* in Java ME (hyphenated) format.
*/
public void addLocale(StringElement locale) {
this.locales.addElement(locale);
}

/**
* Perform the task.
*/
public void execute() throws BuildException {
// Verify the data is present
// (This could be improved with more detailed
// error checking and a more precise error message…)
if((descriptor == null) ||
(source == null) ||
(destination == null) ||
(name == null) ||
(locales.size() == 0)) {
throw new BuildException(“Missing data for resource construction.”);
}
// build the argument array:
String[] args = new String[4 + locales.size()];
args[0] = descriptor;
args[1] = source;
args[2] = destination;
args[3] = name;
for(int i = 0; i < locales.size(); i++) {
args[i + 4] = ((StringElement)(locales.elementAt(i))).toString();
}
// call the ResourceMaker utility:
ResourceMaker.main(args);
}

}
The way it works is pretty simple: For any value that you’d like the task to have access to, you create a corresponding attribute. In this case, the resource file to create is described in a special XML file whose URL on the local computer is “/home/carol/JSR_238_RI_1_0/demos/MoonDemo/src/res.xml” which is the value of the attribute I’ve named “descriptor” in the “buildres” task in the build.xml file. So in the corresponding BuildResources.java class, all I need to do is write a “setDescriptor()” method, and Ant automatically finds the right method to call to set the data by looking at the method name. Once Ant has called all of the relevant data-setting methods, it calls execute() to run the task.

In addition to creating a custom task for this example, I’ve also written a custom type. The difference between a task and a type is that a type is not executed. Ant fills the data using the same types of setter methods as for an Ant task, but there is no “execute()” method. For this reason — since none of the setter methods are standard — there isn’t a particular class to extend or interface to implement. Any class with standard Java-style setters could potentially be used as an Ant “type” that could be filled with data from an Ant build file.

In this example, the locale strings are given by a custom “StringElement” type that I’ve written for this example. Notice that in the BuildResource.java code I’ve included an “addLocale(StringElement locale)” method that tells the Ant code to expect that the BuildResources task may contain nested elements called “locale” that are of type “StringElement.” Ant will give you an error message if your build file contains tasks with attributes or nested elements that don’t have corresponding set or add methods in the code.

Here’s the code for StringElement.java. Note that you can see from the classpaths in the above build file that this class and BuildResource.java need to be compiled into a directory that is called “/home/carol/myAntTask/bin”.

package net.frog_parrot.ant;

public class StringElement {

String text;

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

public String toString() {
return text;
}

}
For more information on writing your own ant task, please see the Ant Developer Manual.

Advertisements

2 comments so far

  1. Livette on

    Nice blog!

  2. […] it’s written in Java, you can call this directly from your Ant build script (see this post for an example of calling an arbitrary Java program from an Ant script). That way you can actually […]


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: