Creating a Java extension

A Java extension to Tcl is like a C extension, in that it provides a very flexible, but sometimes tedious, mechanism for defining new Tcl commands. In this section, we'll create (more-or-less) the same extension as JTimer, but access it as the Tcl command jtimer.

The steps in the process are:

  1. Create a class that implements the tcl.lang.Command interface.
  2. Create a subclass of tcl.lang.Extension.
  3. Override the init method of the extension class to create an instance of the command object, and add it to the Tcl interpreter with the createCommand method.
  4. In your Tcl code, call java::load to load the extension.
Let's look at these in more detail. Here's the class that implements the Command interface, in the file tutorial/tcltk98/JTimerCommand.java:
package tutorial.tcltk98;
import tcl.lang.*;
import java.util.*;

public class JTimerCommand implements Command {
  long startTime = 0;
  public void cmdProc(Interp interp, TclObject argv[])
    throws TclException {
    if ( argv.length != 2 ) {
      throw new TclNumArgsException(interp, 0, argv, 
				    "jtimer \"start\" | \"stop\"");
    }
    String mode = argv[1].toString();

    if ( mode.equals("start") ) {
      GregorianCalendar cal = new GregorianCalendar();
      startTime = cal.getTime().getTime();

    } else if ( mode.equals("stop") ) {
      GregorianCalendar cal = new GregorianCalendar();
      long elapsedTime = cal.getTime().getTime() - startTime;
      interp.setResult(Long.toString(elapsedTime));

    } else {
      throw new TclException(interp, "Expected \"start\" or \"stop\"");
    }
  }
}
Well, so the nice clean code from the JTimer class suddenly got a little dense. First, notice that this class implements the cmdProc method defined in the tcl.lang.Command interface. The first argument is the Tcl interpreter, and the second is an array of TclObjects. The method checks if there are two arguments -- the command name and the mode argument -- and throws an exception if not. Then it gets the Java string value of the mode argument from the corresponding TclObject.
Implements an interface??
Java has inheritance, like most object-oriented languages. It also has interfaces, which contain nothing but method declarations. A class can only inherit from a single parent class, but it can implement as many interfaces as it likes, meaning that it provides actual code for the methods specified by those interfaces. In general, interfaces are a valuable design and coding tool, as they allow classes to play roles in multiple collaborations with little restriction. (Not everybody likes interfaces -- people used to multiple inheritance prefer that.)

Having got that far, we do a series of string comparisons to figure out what the argument is. If it's "start" or "stop," do more-or-less the same stuff as we did in JTimer. There is one key difference: the result from the command is given to the interpreter by calling its setResult method. Finally, if the mode isn't either "start" or "stop," throw an exception that indicates the error.

The subclass of Extension is quite short for our example, as all it does is create and register a single Command object. Here is the code, from tutorial/tcltk98/JTimerExtension.java:

package tutorial.tcltk98;
import tcl.lang.*;

public class JTimerExtension extends Extension {
  public void init (Interp interp) {
    Command cmd = new JTimerCommand();
    interp.createCommand("jtimer",cmd);
  }
}
This class inherits from tcl.lang.Extension, and overrides the init method. When it registers the command object with the Tcl interpreter, it passes the string "jtimer," thus adding the command jtimer to the interpreter.

Let's try it! Compile the files in tutorial/tcltk98/:

  javac JTimerExtension.java JTimerCommand.java

Start wish and go:

  package require java
  java::load tutorial.tcltk98.JTimerExtension
If all is well, this code will just return silently. The java::load command searches for the given class, loads it, and calls its init method.

We now have a jtimer command in our Tcl interpreter. We can try it as we did for the JTimer facade:

  jtimer start; after 1000 {puts [jtimer stop]}