Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Web Development

TclBlend: Blending Tcl and Java


Dr. Dobb's Journal February 1998: TclBlend: Blending Tcl and Java

Manipulating classes for testing purposes

Scott works at the Sun Microsystems Laboratories division of Sun Microsystems Inc. He can be reached at scott.stanton@ eng.sun.com.


Traditionally, Tcl has been a two-language solution. Tcl scripts take advantage of domain-specific extensions implemented in C. Over the last few years, Tcl has evolved into a portable scripting solution that lets you write a single script that runs without modification on Windows, Macintosh, and most UNIX-based systems. Unfortunately, the second half of the solution, C, is much less portable. Java, with its "write once, run anywhere" philosophy, is the obvious choice for a companion language to Tcl. To address the concerns of extension writers and take advantage of the new systems being designed for Java, we wrote TclBlend.

TclBlend provides two new capabilities to the Tcl system. First, TclBlend introduces new Tcl commands that let you directly manipulate Java objects without having to write any Java code. The reflection classes in JDK 1.1 make it possible to invoke methods and access fields on arbitrary objects. For more information on the reflection API, see "Java Reflection," by Paul Tremblett, DDJ, January 1998 (available electronically; see "Resource Center," page 3). TclBlend takes advantage of these capabilities to provide a dynamic interface to Java. In addition, TclBlend provides access to the Tcl interpreter interfaces through a set of Java classes. Using these classes, you can evaluate scripts and add commands to the Tcl interpreter.

TclBlend has a number of potential uses in environments where groups are currently using Java or are considering adopting Java for future projects. Having an interactive interpreter that allows full access to the Java class interfaces makes exploratory programming more feasible. The immediate feedback and short turnaround time is ideal for prototyping and rapid development. Because of its ease of integration into both C and Java code, TclBlend makes Tcl an excellent way to migrate existing code into a Java environment.

In this article, I'll describe TclBlend and show how you can use it to manipulate the public interface of a class for black-box testing purposes. For development teams already invested in Java, TclBlend makes a good harness for writing testing scripts. The standard Tcl distribution comes with an extensive test suite that contains a highly useful generic testing framework. In testing our own Java code, we were able to use the generic test facility and the TclBlend commands to build a set of coverage and regression tests as we were writing the Java code. If we had needed to write our tests in Java, we probably would not have had time to actually write the code we were supposed to test.

The TclBlend sources, as well as binaries for Solaris and Windows NT, are available at http://sunscript.sun.com/java/ and from DDJ (see "Resource Center," page 3).

Using Java from Tcl: Reflection

TclBlend contains a new Tcl package called "java" that adds a number of new commands to the Tcl interpreter. These commands hide the details of using the reflection APIs. To load the package into an interpreter, a script must issue the "package require java" command. When the package is loaded, it checks to see if there is already a Java Virtual Machine (JVM) running in the current process. If no JVM exists, it starts a new one; otherwise, it simply attaches to the existing one. Once TclBlend has successfully attached to a JVM, it creates a number of Tcl commands in the "java" namespace.

To test a Java class, we first need some way of getting an instance of the class to test. The java::new command takes the name of a constructor and the arguments to the constructor, and returns a newly allocated object. In Example 1(a), the script creates a new instance of the Simple class, passing the string a "new value" as the argument to the constructor. The result of the call to java::new is an object handle that refers to the new instance of the Simple class (see Listing One

Object handles returned by java::new act just like object references in Java. An object handle can be passed as an argument, and it can also be used as the object in a method call. If an object handle appears where a Tcl command is expected, the first argument to the command is taken as a method name and the remaining arguments are passed to the method. Example 1(b) shows how a script can create an object, then use the object handle to invoke a method on the object. The return value of the method call is used as the result of the Tcl command.

In some cases, a script may need to test a class that contains overloaded methods. TclBlend tries to pick the right method heuristically, based on the number of arguments passed by the script. Unfortunately, this doesn't always work because some calls are ambiguous. For instance, Example 2(a) illustrates a common situation where there are multiple constructors that take the same number of arguments. In Java, these two constructors are distinguished at the call site by the type of the objects that are passed in.

In Tcl, everything is a string, so it isn't possible to tell if a string like "42" is really an integer or a string constant. In such cases, a script must indicate to TclBlend which of the overloaded methods to use. Example 2(b) illustrates how a script can identify which constructor should be used. In place of the name of the method or constructor, the script can pass a signature list that consists of the method name followed by the types of the arguments. TclBlend uses the signature list to pick the correct method.

TclBlend provides several other new commands that make using Java classes from Tcl more convenient. There are commands for setting and getting fields of objects, invoking static methods, and introspecting on the Java class hierarchy. Table 1 summarizes the new commands provided by TclBlend. Most of these commands operate on Java object handles or class names like the commands we've already seen.

Dynamic Testing

With just java::new and method-calling operations, it is possible to create fairly sophisticated test scripts. Example 3 is a table-driven test script that creates an instance of the Simple2 class (see Listing Two), then checks the return values of all of the get methods to verify that the fields were set correctly. This script demonstrates two important features of the TclBlend system.

First, the names of the accessor methods are generated dynamically from the testdata variable. The script computes the name of the method by concatenating the contents of the name variable (Prop1, for instance) with the string get. The resulting string is then used to determine which method to invoke. Because TclBlend uses the reflection mechanism, the exact method name does not need to be known until run time.

The second detail to notice is how the script treats the various primitive types. Integers, Strings, and Boolean values are passed into the method-invocation line with no explicit type conversions. TclBlend handles type conversions automatically, based on the signature of the method being invoked. So, when the script calls getProp1, TclBlend determines that an int value is needed, and it converts the literal "42" into the correct integer value before passing it to the method. Later, when the same script line calls getProp3, TclBlend converts the argument to a Boolean. TclBlend performs automatic conversions for all of the primitive types and java.lang.String. All other types are treated as object handles and obey the usual Java type rules.

The combination of dynamic method selection and automatic type conversion is extremely useful and can save a lot of coding time. Operations that might require several separate statements with explicit casts in Java can be written more succinctly and with less repetition using TclBlend. The cost of this convenience is a lack of static type checking. If you make type-related mistakes in your Tcl code, you won't notice until you get an error at run time.

Error Handling

So, what happens if a test script causes the Java code to throw an exception? TclBlend catches all exceptions thrown by Java, including run-time exceptions like java.lang.NullPointerException. The Java exceptions are converted into Tcl errors so they can be handled in the normal way (using the catch statement). The string representation of the exception is returned as the result of the method invocation. In addition, the exception object is stored in the errorCode global variable in case a script needs access to more information.

Example 4 illustrates how a script might process an exception that was thrown during a method invocation (Listing Three presents Simple3). When the Simple3.fails() method throws an IllegalArgumentException, the object command generates a Tcl-level error. The catch statement traps the error and stores the error message in the msg variable. The script displays the message to the user, then uses the exception object to print a Java stack trace. The stack trace usually contains enough information to pinpoint the problem fairly quickly.

Using Tcl from Java

So far, all of the classes discussed have had fairly simple functional interfaces. Not all classes are this straightforward. Often, an API contains interfaces that the caller is expected to implement in order to receive callbacks from some object. This poses a special challenge for testing, since a typical test suite may need to create dozens of different callbacks to cover all of the interesting cases for just a single interface. Even if an API has as few as five different callback interfaces, the number of special classes needed for testing purposes can quickly become intractable.

A better solution is to write a single class for each interface that hands control over to a Tcl interpreter for further processing. To do this, however, there needs to be a way of telling the Tcl interpreter to evaluate a script from Java code. TclBlend contains the tcl.lang.Interp class, which provides interfaces for evaluating scripts, getting and setting variables, and adding new commands to the interpreter.

The tcl.lang.Interp class encapsulates the interfaces used to create and manipulate Tcl interpreters from Java. The constructor for the Interp class initializes a new interpreter context. The Interp class (see Listing Four) provides several methods for modifying the state of an interpreter. The eval(String) method takes a script and executes it inside the interpreter. The result of the script is stored inside the interpreter object. The getResult() method retrieves the result of the last script execution. If an error occurred during the execution of the script, the eval method will throw a tcl.lang.TclException, and the result will contain an error message.

Manipulating Tcl Values

In Tcl, all values are fundamentally strings. This contributes to the ease with which Tcl manipulates different data types. However, for performance reasons, Tcl caches alternate forms for the various strings in scripts. For example, once the string "4.367" has been converted to a floating-point number, it is cheaper to keep the numeric representation around rather than reparsing the string each time. For this reason, all values in Tcl are stored as instances of the tcl.lang.TclObject class, which encapsulates a string and one alternate representation of the string. Once a value has been retrieved in a particular form, the parsed value is cached in the TclObject so that it does not have to be reparsed the next time it is needed.

The string representation of a TclObject can always be retrieved using the toString() method. Other representations are retrieved by calling type-specific methods, such as TclInteger.get(obj), which returns an int value. Tcl supports several types of value, including some complicated types like TclList. One of the more important types for testing Java code is the ReflectObject type, which represents a Java object handle like those returned by java::new. The ReflectObject.newInstance(Interp, Object) static method returns a new TclObject that represents a given Java object.

Handling Callbacks from Tcl

Listing Five demonstrates how to put all of this together to write a wrapper class to use in a callback interface. The TestActionListener class implements the AWT ActionListener interface in a completely generic fashion. A Tcl script can create a new TestActionListener instance, specifying a script that should be run in an interpreter. When an ActionEvent is delivered to an instance of this class, it puts the ActionEvent object into the "event" Tcl variable in the interpreter, then calls the script that was specified in the constructor. This class needs to be written only once because its behavior is determined by the script that is passed to it at creation time.

In Listing Six, you can see how a Tcl script can use this wrapper class to bind a script to an event generated by an AWT Button object. The example code creates a trivial AWT application containing a single Button instance. It then creates a new TestActionListener instance, passing in both the current interpreter handle and a script to be executed. Rather than creating a new Interp instance, the script uses the java::getinterp command to retrieve an object handle for the current interpreter. Once it has a new TestActionListener instance, the test script calls addActionListener to attach the wrapper object to the Button instance.

When the button is pressed, AWT sends an ActionEvent to the button object. The button then calls the actionPerformed methods of all of the registered listeners, causing the action script to be invoked. In this test case, the script simply compares the action command with the expected value and indicates success or failure.

Future work

TclBlend, even in its current prefinal state, is a useful addition to your programming toolchest if you are trying to produce Java code under today's "web-time" constraints. The TclBlend project is working to ensure that you can benefit from the flexibility that the two-language Tcl solution offers.

We are planning to take TclBlend toward direct JavaBean support. By the time you read this article, TclBlend should have support for manipulating Bean properties and binding scripts to Bean events. It is our intention that TclBlend be a full Bean-hosting environment.

We also plan to make more of the Tcl C-library interfaces available to Java programmers. The current implementation contains everything needed to write new Tcl commands in Java, but not much more than that.

In support of the "100% Pure Java" effort, we are working on a version of Tcl that will run in a Java-only environment. This project is called "Jacl" and is based on work done by Ioi Lam while he was at Cornell University. This work should make TclBlend a viable strategy even for environments where no native code is allowed.

DDJ

Listing One

public class Simple {    public Simple(String s) {
        contents = s;
        }
        public String toString() {
        return contents;
    }
    public String contents = "default value";
}


</p>

Back to Article

Listing Two

public class Simple2 {    public Simple2(int arg1, String arg2, boolean arg3) {
        ival = arg1;
        sval = arg2;
        bval = arg3;
    }
    public int getProp1() { return ival; }
    public String getProp2() { return sval; }
    public boolean getProp3() { return bval; }


</p>
    private int ival;
    private String sval;
    private boolean bval;
}

Back to Article

Listing Three

public class Simple3 {    public void fails() {
        throw new IllegalArgumentException("Bad argument");
    }
}

Back to Article

Listing Four

public class Interp {    public Interp();
    public void eval(String script) throws TclException;
    public void setVar(String name, TclObject value) throws TclException;
    public TclObject getVar(String name) throws TclException;
    public TclObject getResult();
}

Back to Article

Listing Five

import java.awt.event.*;public class TestActionListener implements ActionListener {
    public TestActionListener(tcl.lang.Interp i, String s) {
        interp = i;
        script = s;
    }
    public void actionPerformed(ActionEvent e) {
        try {
            interp.setVar("event",
            tcl.lang.ReflectObject.newInstance(interp, e), 0);
            interp.eval(script);
        } catch (tcl.lang.TclException te) {
        System.out.println("error in ActionListener script: "
            + interp.getResult());
        }
    }
    private tcl.lang.Interp interp;
    private String script;
}

Back to Article

Listing Six

set b [java::new java.awt.Button "Quit"]set f [java::new java.awt.Frame "Demo"]
$f {add java.awt.Component} $b
$f pack
$f show


</p>
set al [java::new TestActionListener [java::getinterp] {
    if {[string compare [$event getActionCommand] "Quit"] == 0} {
        puts "test succeeded"
    } else {
        puts "testfailed"
    }
}]
$b addActionListener $al

Back to Article


Copyright © 1998, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.