Skip to main content

Scripting with Jython Instead of XML

June 10, 2003




XML is a popular medium for representing, transforming, and sharing data among applications. Common examples include configuration files for web applications, inter-application messaging using SOAP, and user interface transformation using XSLT. Some say that XML is so great because it's "everything and nothing." This has led to applications of XML to areas outside of its element. XML is often used as the basis of a custom scripting language. As a result, common programmatic constructs are duplicated and the code is difficult to understand; XML tools are not designed to support this.

In this article, I will show a few examples from the Java community using XML as a scripting language. I will also show those same examples in Jython, in order to demonstrate the usefulness of a scripting language. There are many other Java-based scripting languages. Jython is simply Python implemented in Java (lagging a few releases behind). Jython and Java objects can communicate without any special bindings. This means Jython objects can extend Java objects and more. Jython syntax will feel familiar, with notable exceptions, such as: lines do not end with semicolons, and tabs, rather than curly braces, are used to delineate code blocks. This article does not suggest that Jython is better than XML, but rather that each is particularly suited for solving different problems. This article explores some common tasks performed with XML and how they might be better implemented in a Java scripting language. A future article will focus on the actual implementation of one of these tasks, showing in detail the benefits of Java scripting.

Scripting Tests with XML and Jython

We'll start by exploring a testing infrastructure that my company is developing. This system was first built using XML and then reimplemented and extended using Jython. These two examples highlight some of the problems encountered when you implement scripts in XML. In this case, we will see how these limitations can be addressed by swapping out the XML for a full-powered scripting language such as Jython.

Replacing an XML Test Script with Jython

In this first example, you will already see a slight benefit in replacing XML code with Jython. Because the XML maps so clearly to the Jython, you will get a clear idea of what is going on. The Jython code is cleaner and more familiar to Java programmers due, in this case, to the built-in ability to define and use variables. Consider this XML implementation of a test of a stock trading system: two traders log in, each with Yahoo (nyse:YHOO) stock to trade. The trade is executed and both traders log out. To avoid duplicated data, we have introduced a data object used to define all of the stocks in one place so they can be referenced by the same stock objects throughout the test.

<test>
  <stocks>
    <stock symbol="YHOO" ID1="..." ID2="..." ID3="..." />
  </stocks>

  <login trader="Trader1" password="pswd">
    <stocks>
      <stock>${YHOO}</stock>
    </stocks>
  </login>

  <login trader="Trader2" password="pswd">
    <stocks>
      <stock>${YHOO}</stock>
    </stocks>
  </login>

  <trade stock="YHOO" buyer="Trader1" seller="Trader2">

  <logout trader="Trader1">
  <logout trader="Trader2">
</test>

In this test, ${YHOO} references the YHOO stock defined in the <stocks> element at the beginning of <test>. The introduction of variables eliminates duplicated code but requires additional code in the XML parser for the tests to be parsed and function properly. In addition, the presence of stock objects introduces issues of scope. If one test references another test and each has an object with the same name, which takes precedence? This requires technical specification for scooping, and even more custom code in the parser and controller to make the tests function properly. The issue of argument passing came up and we implemented that as well, which required another set of technical requirements and code changes in the parser and controller.

It is easy to get trapped in this sort of loop. You create incremental fixes to your current problems and forget to stand back and take another look from the broader perspective. Fortunately for those of us working on this particular project, a few forward-thinking team members pointed out that we were creating an entire language in XML and suggested that we move to a full-blown scripting language like Jython. That way, all of the scooping, variable passing, and other programmatic constructs could be handled by language experts, and we could spend our time writing the testing system instead of a proprietary XML-based language.

Here is the same example implemented in Jython:

yhoo = stock(symbol="YHOO",  ID1="...", ID2="...", ID3="...")

trader1 = login(trader="Trader1", password="pswd")
trader2 = login(trader="Trader1", password="pswd")

trader1.addStock(yhoo)
trader2.addStock(yhoo)

trade(trader1, trader2, yhoo)

logout(trader1)
logout(trader2)

The Jython code for this example is quite a bit cleaner than the XML. All of the variable parsing, scooping, passing, assigning, and referencing code that we had to write in the parser and controller came free with Jython.

Reducing Tedium in Testing

The benefits of using a scripting language go far beyond simply getting variable functionality for free. The following example shows how we benefit from different built-in collections and iteration constructs. In this example, we had a set of five user-configurable default response messages in our client application, and each message had seven steps to verify that the field was being retrieved and edited properly. As your eyes glaze over in the following listing, notice that the seven steps have to be repeated almost verbatim, changing values at each pass. Here is the complete test to verify all five fields:

<test>
  <login trader="Trader1" password="pswd"/>

  <verify-default-response field="default1" >
    Hello McFly
  </ verify-default-response>
  <verify-default-response field="default1" shouldFail="true">
    New Message
  </ verify-default-response>
  <edit-default-response field="default1">
    New Message
  </ set-default-response>
  <verify-default-response field="default1">
    New Message
  </ verify-default-response>
  <verify-default-response field="default1" shouldFail="true">
    Hello McFly
  </ verify-default-response>
  <edit-default-response field="default1" >
     Hello McFly
  </ verify-default-response>
  <verify-default-response field="default1">
    Hello McFly
  </ verify-default-response>

  <verify-default-response field="default2" >
    Wazzzup
  </ verify-default-response>
  <verify-default-response field="default2" shouldFail="true">
    New Message
  </ verify-default-response>
  <edit-default-response field="default2">
    New Message
  </ set-default-response>
  <verify-default-response field="default2">
    New Message
  </ verify-default-response>
  <verify-default-response field="default2" shouldFail="true">
    Wazzzup
  </ verify-default-response>
  <edit-default-response field="default2" >
    Wazzzup
  </ verify-default-response>
  <verify-default-response field="default2">
    Wazzzup
  </ verify-default-response>

  <verify-default-response field="default3" >
    Your mother was a hamster
  </ verify-default-response>
  <verify-default-response field="default3" shouldFail="true">
    New Message
  </ verify-default-response>
  <edit-default-response    field="default3">
    New Message
  </ set-default-response>
  <verify-default-response field="default3">
    New Message
  </ verify-default-response>
  <verify-default-response field="default3" shouldFail="true">
    Your mother was a hamster
  </ verify-default-response>
  <edit-default-response    field="default3" >
    Your mother was a hamster
  </ verify-default-response>
  <verify-default-response field="default3">
    Your mother was a hamster
  </ verify-default-response>

  <verify-default-response field="default4" >
    Hey Josephus, Hey @$#!
  </ verify-default-response>
  <verify-default-response field="default4" shouldFail="true">
    New Message
  </ verify-default-response>
  <edit-default-response    field="default4">
    New Message
  </ set-default-response>
  <verify-default-response field="default4">
    New Message
  </ verify-default-response>
  <verify-default-response field="default4" shouldFail="true">
    Hey Josephus, Hey @$#!
  </ verify-default-response>
  <edit-default-response    field="default4" >
    Hey Josephus, Hey @$#!
  </ verify-default-response>
  <verify-default-response field="default4">
    Hey Josephus, Hey @$#!
  </ verify-default-response>

  <verify-default-response field="default5" >
    Ni!
  </ verify-default-response>
  <verify-default-response field="default5" shouldFail="true">
    New Message
  </ verify-default-response>
  <edit-default-response    field="default5">
    New Message
  </ set-default-response>
  <verify-default-response field="default5">
    New Message
  </ verify-default-response>
  <verify-default-response field="default5" shouldFail="true">
    Ni!
  </ verify-default-response>
  <edit-default-response    field="default5" >
    Ni!
  </ verify-default-response>
  <verify-default-response field="default5">
    Ni!
  </ verify-default-response>

  <logout trader="Trader1"/>
</test>

This is not an optimal solution. Writing this test requires a very time-consuming and error-prone process of copying and pasting the test block and replacing the test messages. This poses a number of additional problems, including the fact that there is no centralized message list for an easy default message, making it hard to maintain. Additionally, it is difficult to understand what the test is doing because of its verbosity. Of course, there are other XML tools available that will allow us to code this example differently, but let us look, instead, at using a language built for scripting.

Jython test code for this example is extremely simple. We can leverage the benefits of a scripting language and loop through the messages, calling the same function for each one. Additionally, we can abstract the set of default messages into a Jython dictionary (similar to a Java hashtable). Here is the Jython version:

defaultMessages = {
    "message1": "Hello McFly",
    "message2": "Wazzzup",
    "message3": "Your mother was a hamster",
    "message4": "Hey Josephus, Hey @$#!,
    "message5": "Ni!"
}

def EditNegotiationChatMessagesTest():

    loginTraderTask(trader="Trader1", password="pswd")

    for name, value in defaultMessages.items():
        verifyChatMessages(**{name:value})
        verifyChatMessages(shouldFail=True, **{name:"New Message"})
        editChatMessages(**{name:"New Message"})
        verifyChatMessages(**{name:"New Message"})
        verifyMessages(shouldFail=True, **{name:value})
        editChatMessages(**{name:value})
        verifyChatMessage(**{name:value})

    logoutTraderTask(trader="Trader1")

This is a substantial improvement from the XML test. First of all, the Jython version reduces errors in test writing while making it easier to read, since the loop clearly indicates that the same set of functions is being performed. It is also easier to maintain the Jython test, since a change in the loop changes the execution of each of the five tests. Additionally, the default messages are abstracted again, reducing errors and increasing readability and maintainability.

User Interface Scripting

With the J2SE 1.4 release you can use the java.beans.XMLEncoder and java.beans.XMLDecoder classes to persist Swing components using XML. There are other XML-based projects that go further. The JellySwing project found on the Apache Jakarta Project site uses scripts encoded as XML documents to define Swing front ends for your applications. This allows you to minimize the code required to build Swing UIs by as much as 30% or more. Additionally, the use of XML rather than Java Swing code allows for such XML techniques as XSL transforms to generate UIs. But JellySwing isn't good at everything, particularly when the language boundary is crossed. We have just seen the benefits of replacing XML-based scripting with Jython. Now we'll take a look at which JellySwing tasks might be better accomplished with a scripting language such as Jython.

In JellySwing, the Swing user interfaces are specified in XML, and a controller interprets the description at runtime and builds the screens. The following example depicts a basic Swing UI with a JMenuBar and three JButtons, each printing a message to the log.

<?xml version="1.0"?>
<j:jelly
      xmlns:j="jelly:core"
      xmlns="jelly:swing"
      xmlns:log="jelly:log"
      xmlns:define="jelly:define">
  <define:script var="onClosing">
    <log:info>The frame is closing via event: ${event}</log:info>
  </define:script>
  <frame title="This is a frame" var="frame" location="100,100" size="800,400">
    <menuBar>
      <menu text="File">
        <menuItem>
          <action name="New">
          <log:info>clicked on the New menu item!</log:info>
          </action>
        </menuItem>
        <menuItem>
          <action name="Open">
          <log:info>Popup a file dialog!</log:info>
          </action>
        </menuItem>
        <separator/>
        <menuItem text="Save" enabled="false" />
      </menu>
    </menuBar>
    <panel>
      <titledBorder title="Sample Border Title"/>
        <tableLayout>
          <tr>
            <td>
              <button>
                <action name="Button One">
                  <log:info>Clicked on Button One</log:info>
                </action>
              </button>
            </td>
            <td>
              <button>
              <action name="Button Two">
                  <log:info>Clicked on Button Two</log:info>
                </action>
              </button>
            </td>
            <td>
              <button>
                <action name="Button Three">
                  <log:info>Clicked on Button three</log:info>
                </action>
          </button>
            </td>
          </tr>
        </tableLayout>
      </panel>
    <windowListener var="event" closing="${onClosing}"/>
  </frame>
  ${frame.show()}
</j:jelly>

You may not notice this immediately, but the JellySwing XML is approximately 40 lines shorter then the comparable Java Swing code; already a huge benefit. So let's take a closer look at the code. You can classify the JellySwing XML in three basic categories; most of the example is declarative, with several components added, attributes set, values declared, and layouts configured. This is much like the code that would be generated by XMLEncoder. Here is an example of this declarative type of code.

<menuItem text="Save" enabled="false" />

The script also contains embedded Java code. As an example, the next-to-last line in the script is a method call to the show() method of the frame just constructed.

${frame.show()}

Finally, you can identify instances of event handling like the following, which attaches a WindowListener to the JFrame and logs a message:

<define:script var="onClosing">
  <log:info>The frame is closing via event: ${event}</log:info>
</define:script>

This attaches a window-closing listener that logs a message when the window is closing, including the event. Here is a different example that not only prints text information, but also retrieves values from the other components, like the TextField text:

<toolBar style="vertical">
  <toolItem text="Click Me" toolTipText="I am a ToolBar you can click">
    <onEvent type="Selection">
      <log:info>Clicked event ${event} and TF contains ${textField.text}</log:info>
    </onEvent>
  </toolItem>
</toolbar>

In many ways, this feels a lot like embedding Java code in JSPs; this is Java code embedded in XML. Although these two event-handling examples are fairly tame, this injection of Java code occurs throughout JellySwing XML. The following example, taken from an example on the JellySwing web site, includes a large percentage of Java code used to set table values:

<tableItem var="row"/>
  ${row.setText(0, 'James')}
  ${row.setText(1, '33')}
<tableItem var="row"/>
  ${row.setText(0, 'Bob')}
  ${row.setText(1, '30')}

Although the example states that this would be implemented differently, it clearly shows a fundamental problem with JellySwing. XML is being used as a programming language. Essentially, Java code is embedded in XML. This can cause several problems in development. Code writing is more difficult without development tools like code completion and introspection. Debugging is also impossible, since an IDE debugger can't debug this embedded Java.

To avoid embedding Java code in XML, let's look how this could be implemented in Jython. The following is a port of the JellySwing XML to Jython:

from javax.swing import *
#from java.awt.event import *
from java.awt import *
from java.lang import *

win = JFrame("This is a frame")
win.bounds = 100,100,100,100
win.defaultCloseOperation = 3 #EXIT_ON_CLOSE

menuBar = JMenuBar()
fileMenu = JMenu("File")
menuBar.add(fileMenu);

newMenuItem = JMenuItem("New")
newMenuItem.actionPerformed=lambda event : System.out.println("Clicked on New Menu Item")
fileMenu.add(newMenuItem)

openMenuItem = JMenuItem("Open")
openMenuItem.actionPerformed=lambda event : System.out.println("Clicked on Open Menu Item")
fileMenu.add(openMenuItem)

saveMenuItem = JMenuItem("Save")
saveMenuItem.actionPerformed=lambda event : System.out.println("Clicked on Save Menu Item")
fileMenu.add(saveMenuItem)
saveMenuItem.enabled = 0 #false

win.JMenuBar = menuBar

contentPane = win.contentPane
contentPane.layout = GridLayout()

buttonOne = JButton("Button One")
buttonOne.actionPerformed=lambda event : System.out.println("Clicked on Button One")
contentPane.add(buttonOne)

buttonTwo = JButton("Button Two")
buttonTwo.actionPerformed=lambda event : System.out.println("Clicked on Button Two")
contentPane.add(buttonTwo)

buttonThree = JButton("Button Three")
buttonThree.actionPerformed=lambda event : System.out.println("Clicked on Button Three")
contentPane.add(buttonThree)

win.pack()
win.show()

Again the Jython code is even more compact than the JellySwing XML. In fact, the Jython version is approximately 20 lines less than the comparable JellySwing version, and 60 lines shorter than the JavaSwing code. Additionally, it is more readable by Java programmers, since Jython syntax is closer to regular Java. Plus, we get the benefit of language interoperability. We can write the user interface code in Jython and the event code in Java, we could implement all of the event code and UI code in Jython, and most importantly, we can refactor the code between Jython and Java as our project grows. Jelly and tools like it may be appropriate tools for the declarative portions of specifying a Swing GUI, but fall short once real code is introduced into the XML.

ANT

ANT is a great tool. Unifying build and package commands into a file easily read and distributed in XML has been a great help to all sorts of projects. But the XML representation in ANT has become a scripting language in and of itself. The ANT syntax may be so familiar that you no longer remember your first experiences with complex build scripts. Even the use of the term "build script" is an indication of the possible inappropriate use of XML in this setting.

There is a visible struggle in the use of XML as a programming language in ANT. Note that properties are used in ANT scripts. We saw the problems with introducing variables into XML in our first example. In ANT, the result is that properties are represented in the form ${PROPERTY }. This causes confusion with many ANT developers. For example, you can't change a property once its been set, because it is a property, not a variable. This is exactly the kind of trouble you run into trying to make XML work as a programming language. To further illustrate the point, here is an example using ANT's native conditional task:

<condition property="failmessage" value="Files up to date - build not required">
  <and>
    <isset property="build.notRequired"/>
    <not>
      <isset property="sync.external"/>
    </not>
  </and>
</condition>

This sample sets the failmessage property to Files up to date - build not required only if build.notrequired is set and sync.external is not set. In my mind, this is completely illegible. This task is going over the edge implementing conditional functionality in XML, clearly crossing the boundary from XML markup syntax to a programming language.

The ANT community continues to push ANT further in the direction of a custom scripting language. Since ANT was developed, there have been plugins created for implementing conditionals, loops, and other programming constructs in ANT XML syntax. With these techniques, they have created a programming language in XML! The following is an example taken from the ant-contrib Sourceforge project implementing try/catch functionality in ANT XML:

<trycatch property="foo" reference="bar">
  <try>
    <fail>Tada!</fail>
  </try>
  <catch>
    <echo>In <catch>.</echo>
  </catch>
  <finally>
    <echo>In <finally>.</echo>
  </finally>
</trycatch>

It might be nice to use these conditionals, loops, and variables in ANT without "programming" in XML. If these complex build scripts need to be written, then what we really need is a scripting-language-based ANT, not ANT plugins. An ANT that uses Jython, for example, instead of XML. Then we would have the power of an entire programming language at our fingertips, with all of the developing and debugging tools that go with it. Here is an example of what the previous conditional example might look like in Jython.

if (buildNotRequired && !syncExternal())
failmessage = "Files up to date - build not reqired"

Here is another basic ANT task better represented in Jython. This example shows the use of an ANT fileset to sign all of the .jar files in a directory.

<signjar alias="${alias}" storepass="${pass}" keystore="${keystore}">
<fileset dir="${jarDir}" includes="*.jar" excludes="jnet.jar"/>
</signjar>

This same procedure can be written much more legibly in Jython:

for jarFile in getFiles(jarDir, "*.jar" exclude="jnet.jar")
signJar(jarFile, keystore, alias, keypass)

Not only is this easier to write and understand than the XML, but it comes for free -- it's just Jython. We didn't have to implement specific functionality to get this to work; it's simply part of the language. And someone else is maintaining the language, testing it, and releasing it. By contrast, each function that supports filesets in ANT has to be specifically implemented.

Stay Tuned

Next time, I will show you how to implement real working ANT scripts directly in Jython. All of the ANT tasks have underlying Java code that can be called directly from Jython without any "glue." This is because Jython runs in Java and can communicate directly with any Java code. This includes calling the underlying Java-based ANT code in addition to the Java code supporting any ANT plugin. This is really a great example showing the power of a Java-based scripting language.

Conclusion

My bias is that XML is a great tool that is misapplied when it is used as a programming language. You have seen a few examples where the XML markup/programming language boundary has been crossed, and where a scripting language like Jython may be a better alternative. Jython is easier to read and write compared to XML, because of its similar syntax to Java code. Additionally, Jython can communicate with all of your other Java code. So the only real difference between Java code and Jython is syntax, rather than the complete incompatibility between Java and XML.

There is also some hesitation in moving from XML to scripting languages partly because of perceived limits in portability. A build script written in Jython must be rewritten to work in JRuby. If we switch from ANT to another tool, we can always write an interpreter that allows us to continue to use the existing XML scripts with our new tool. This is also a hesitation of developers to use Java scripting languages, since they are arguably less mature then Java and XML and have fewer development tools. But maybe, if we as a community begin to get vocal about when a Java scripting languages should be used, the open source community and our vendors will develop better and stronger tools to support Java scripting languages.

Resources

I wanted to thank the Developers and QA at Liquidnet, as well as the SWT listserv, for ideas and information on Java scripting.

Jonathan Simon is a developer and author specializing in user interaction.
Related Topics >> Web Services and XML   |