Jato: The new kid on the open source block, Part 2

Look in-depth at Java-to-XML translation

1 2 3 Page 2
Page 2 of 3

Jato provides multiple forms for most tags. The Jato Reference Guide calls these different forms tag formats. Listing 3 uses the attribute-tag format called "Property Format" that obtains the new attribute value using a JavaBeans property from the current object. A property formatted tag will always contain a property attribute, which specifies the JavaBeans property to get or set using the current object. Thus, the following &ltJato:attribute> tag sets the path attribute on the root element using the absolutePath beans property:

<Jato:translate key='root'>
   <Jato:attribute name='path' property='absolutePath'/>
   <Jato:inline macro='dir'/>
</Jato:translate>

In this case, the JavaBeans property value absolutePath maps to the File getAbsolutePath() method. The &ltJato:text> tag supports the same formats as the &ltJato:attribute> tag. Following the coding pattern, the following tag sets the text on the current output element using the name property:

 
<file>
  <Jato:text property='name'/>
</file>

The "Invoke Format" provides a format for obtaining values when a class does not provide a beans method. The "Invoke Format" specifies the following:

  • invoke attribute: Specifies the method name to invoke
  • class attribute: Optional attribute that specifies the class on which to invoke the method when calling static methods
  • &ltJato:param> child tags: Optional tags that specify the parameters to pass to the method

The following &ltJato:attribute> tag sets the size attribute on the current <file> output element by invoking the File method length() on the current Jato object:

<file>
   <Jato:attribute name='size' invoke='length'/>
</file>

The length() method does not specify a class attribute or &ltJato:param> tags since it is not static and takes no parameters. For more information on other tags and formats, see the Jato Reference Guide.

Invoke Jato functions

So far, Jato has provided the built-in support for creating elements and adding content to the output document. To generate the permissions attribute, the implementation of a Jato function is required. Remember from the problem description, the file read and write permissions must have the form:

<file permissions='rw'>

In the permissions attribute, 'r' means the file has read permissions and 'w' means the file has write permissions. The File class provides the two methods: canRead() and canWrite(), but they return boolean values. Our task: construct a Jato function that will turn the boolean values for read and write permissions into one of the strings 'rw', 'r', 'w', or empty string for no permissions.

To invoke the Jato function, use:

<Jato:attribute name='permissions' function='file-perms'>
   <Jato:param name='read' invoke='canRead'/>
   <Jato:param name='write' invoke='canWrite'/>
</Jato:attribute>

This script invokes the Jato function 'file-perms' and passes two parameters: 'read' and 'write', which you obtain by invoking the canRead() and canWrite() methods on the current object. Notice the &ltJato:param> tag uses the Invoke Format just as the &ltJato:attribute> did previously.

To implement Jato functions, implement the org.jato.JatoFunction interface. The interface is defined as:

public interface JatoFunction {
   public String getName();
   public Object invoke(Properties parms, Interpreter jato, Object thisObj,
                        Element xmlIn, Element xmlOut)
   throws JatoException;
}

The getName() method returns the name of the function as it will be referenced in the script. The invoke() method contains the function implementation. It receives a reference to the current Jato interpreter, the current object, the current input XML element, and the current output XML element. Listing 4 shows the implementation of the file-perms() Jato function. Notice the parameters are passed to the function in a Properties object that allows the parameters to be accessed by name. The invoke() method returns the new value as a String. In this case, the parameters are passed as java.lang.Boolean objects even though canRead() and canWrite() return boolean (lowercase 'b') values. Primitive values will always be encapsulated in a java.lang wrapper class, such Character, Integer, Long, Double, or Float:

Listing 4: Jato function to format file permissions attribute

public class PermFunction implements JatoFunction {
   public String getName() { return "file-perms"; }
   public Object invoke(Properties parms, Interpreter jato, Object thisObj,
                        Element xmlIn, Element xmlOut)
   throws JatoException
   {
      Boolean rperm = (Boolean)parms.get("read");
      Boolean wperm = (Boolean)parms.get("write");
      return "" + (rperm.booleanValue() ?"r" :"") +
                  (wperm.booleanValue() ?"w" :"");
   }
}

Custom Jato functions must be registered before they may be referenced in a script. This is easily accomplished using the InterperterUtil.addFunction(JatoFunction f) method:

public static void main(String args[]) throws Exception {
   //load the built in tags, formats, and functions
   InterpreterUtil.loadDefaultJatoDefs();
   //register custom functions
   InterpreterUtil.addFunction(new PermFunction());
   . . .
}

Use your application's main() method as a convenient place for this registration, as it guarantees just a single occurrence.

Format XML content values

While functions enable developers to easily integrate complex Java functionality into a Jato script, formats allow for complex formatting of XML attributes, text, and other content. Jato includes built-in formats, but you can create custom formats by implementing the org.jato.JatoFormat interface, which resembles the JatoFormat interface. Before being referenced in a script, custom formats must be registered using the InterpreterUtil.addJatoFormat(JatoFormat f) method.

The requirements of our Jato script call for outputting dates with the format:

Day of week,  Month Day,  Year

An example date would be: "Sat, Mar 17, '01." Outputting dates in this fashion requires a format because Java provides the last file-modification date through the File class's lastModified() method, which returns the modified date as the number of milliseconds since January 1, 1970. Specifying dates in terms of milliseconds proves handy for calculations but it's lousy for human readability. In this case, we want to convert the value returned by lastModified() into a human-readable form. Formatting an attribute or text value simply consists of adding a 'format' attribute to a Jato tag:

&ltfile>
<Jato:attribute name='modified' invoke='lastModified' 
                   format="date(EEE, MMM d, ''yy)"/>
</file>

This attribute tag uses the built-in date format and outputs:

<file modified="Sat, Mar 17, '01"/>

The date format uses a java.text.SimpleDateFormat instance to perform formatting. If the format is specified as format="date()", then dates will be formatted using the SimpleDateFormat default format. Otherwise, simply specify a format pattern as documented in the SimpleDateFormat class documentation.

Our completed script is shown in Listing 5:

Listing 5: java-to-xml.xml Jato script

<Jato:defs xmlns:Jato='http://jato.sourceforge.net'>
   <Jato:translate key='root'>
      <Jato:attribute name='path' property='absolutePath'/>
      <Jato:inline macro='dir'/>
   </Jato:translate>
   <Jato:macros>
   <Jato:macro name='dir'>
      <dir>
         <Jato:attribute name='name' property='name'/>
         <Jato:inline macro='info'/>
         <Jato:debug print-elt='true'>Transforming directory</Jato:debug>
         <Jato:invoke method='listFiles'>
            <Jato:if property='directory'>
               <Jato:inline macro='dir'/>
               <Jato:else>
                  <Jato:debug print-object='true'>Transforming file</Jato:debug>
                  <file>
                     <Jato:text property='name'/>
                     <Jato:attribute name='size' invoke='length'/>
                     <Jato:inline macro='info'/>
                  </file>
               </Jato:else>
            </Jato:if>
         </Jato:invoke>
      </dir>
   </Jato:macro>
   <Jato:macro name='info'>
      <Jato:attribute name='modified' invoke='lastModified' format='date'/>
      <Jato:attribute name='permissions' function='file-perms'>
         <Jato:param name='read' invoke='canRead'/>
         <Jato:param name='write' invoke='canWrite'/>
      </Jato:attribute>
   </Jato:macro>
   </Jato:macros>
</Jato:defs>

The associated helper class is shown in Listing 6:

Listing 6: Helper class for java-to-xml.xml Jato script

import org.jdom.*;
import org.jdom.output.*;
import org.jdom.input.*;
import org.jato.*;
import java.io.*;
import java.util.*;
public class FileToXML extends XMLHelperAdapter {
   public FileToXml(File jatoFile) 
   throws JDOMException, IOException, JatoException 
   {
      super(jatoFile);
   }
   public Object getObject(String key) {
      if (key.equalsIgnoreCase("root")) {
         return new File(System.getProperty("user.dir")).getParentFile();
      } else {
         throw new IllegalArgumentException("Key not supported: " + key);
      }
   } 
   public static void main(String args[]) throws Exception {
      //load default tags and formats and add custom functions
      InterpreterUtil.loadDefaultJatoDefs();
      InterpreterUtil.addFunction(new PermFunction());
      JavaToXml jtox = new JavaToXml();
      jtox.setHelperClass("FileToXml");
      jtox.setSaveOption(true, "site.xml");
      org.jdom.Document doc = jtox.transform();
   } 
}

To run this script, simply type:

> java FileToXml

To run the code in the debugger as shown earlier, change the main() as follows:

public static void main(String args[]) throws Exception {
   InterpreterUtil.loadDefaultJatoDefs();
   //register custom functions
   InterpreterUtil.addFunction(new PermFunction());
   //have to turn debug generation on
   InterpreterUtil.setDebugEnabled(true);
   Interpreter jato = InterpreterUtil.createInterpreter(new FileToXml());
   //create and launch the debugger
   JatoDebugger debug = new JatoDebugger(jato);
   jato.attachDebugger(debug);
   Document doc = debug.transform();
   //save the file
   FileOutputStream xmlOS = new FileOutputStream("site.xml");
   new XMLOutputter("   ", true).output(doc, xmlOS);
   xmlOS.close();
}

Bringing up the debugger requires several calls. Before creating the interpreter, you must enable debugging by invoking the InterpreterUtil.setDebugEnabled(true). After creating the Jato interpreter, create a debugger instance and attach it to the interpreter. Instead of invoking the interpreter's transform() method directly, invoke the debugger's transform() method. If you wish to debug your Java code interfaced by Jato, start this program in your favorite Java debugger. You can then set breakpoints in the Java debugger and the Jato debugger and step through the execution of the script and the Java code. Wow, hours of free entertainment!

Create elements using <Jato:element>

So far, all scripts have created elements by placing regular XML tags in the script. Sometimes, however, there is a need to create an element programmatically. XML elements can be explicitly created using the <Jato:element> tag. The <dir> tag in our script could be created as follows:

<Jato:element name='dir' invoke='listFiles' recurse='true'>

This tag instructs Jato to:

  1. invoke='listFiles': Invoke the listFiles() method
  2. name='dir': Create a <dir> element for each object returned by the listFiles() method
  3. recurse='true': Perform the same command on each object returned by the listFiles() method

Of course, listFiles() returns an array of files that includes directories and files, so this command would create <dir> tags for files and directories. Considering that, we need some way to filter the objects returned by listFiles() so that it returns directories only.

Pass parameters to Java methods using <Jato:param>

The File class contains a means to filter the files returned by the listFiles() method by passing a java.io.FileFilter instance to the method. This requires that Jato pass an implementation of the FileFilter interface to the method. To pass parameters to methods, we employ the <Jato:param> tag. However, before looking at passing parameters, let's implement our filter class.

The File class contains a version of listFiles() with the following signature:

File[] listFiles(java.io.FilenameFilter filter)

In this case, FilenameFilter is a single method interface:

public interface FilenameFilter {
   public boolean accept(File dir, String name);
}

In our situation, we would implement FilenameFilter to filter all nondirectory files:

public class DirFilter implements FilenameFilter {
   public boolean accept(File dir, String name) {
      return new File(dir, name).isDirectory();
   }
}

How does it work? If writing straight Java, the code to obtain a list of directories contained in the current directory would be:

File curr = new File(".");         //get current directory
DirFilter df = new DirFilter();    //creat a filter
File list[] = curr.listFiles(df);  //get list of directories

Returning to our Jato example, we need to create an instance of DirFilter and pass it to listFiles() when creating <dir> elements. The new 'dir' macro is shown in Listing 7:

Listing 7: 'dir' macro that uses <Jato:element> to create XML <dir> tags

1 2 3 Page 2
Page 2 of 3