12. Adding Commands to Jess

The Java interface jess.Userfunction represents a single function in the Jess language. You can add new functions to the Jess language simply by writing a class that implements the jess.Userfunction interface (see below for details on how this is done), creating a single instance of this class and installing it into a jess.Rete object using Rete.addUserfunction(). The Userfunction classes you write can maintain state; therefore a Userfunction can cache results across invocations, maintain complex data structures, or keep references to external Java objects for callbacks. A single Userfunction can be a gateway into a complex Java subsystem.

12.1. Writing Extensions

I've made it as easy as possible to add user-defined functions to Jess. There is no system type-checking on arguments, so you don't need to tell the system about your arguments, and values are self-describing, so you don't need to tell the system what type you return. You do, however, need to understand several Jess classes, including jess.Value, jess.Context, and jess.Funcall, as previously discussed in the chapter Introduction to Programming with Jess in Java.

12.1.1. Implementing your Userfunction

To implement the jess.Userfunction interface, you need to implement only two methods: getName() and call(). Here's an example of a class called 'MyUpcase' that implements the Jess function my-upcase, which expects a String as an argument, and returns the string in uppercase.
    
import jess.*;

public class ExMyUpcase implements Userfunction {
    // The name method returns the name by which
    // the function will appear in Jess code.
    public String getName() { return "my-upcase"; }

    public Value call(ValueVector vv, Context context) throws JessException {
        String result = vv.get(1).stringValue(context).toUpperCase();
        return new Value(result, RU.STRING);
      }
  }

The call() method does the business of your Userfunction. When call() is invoked, the first argument will be a ValueVector representation of the Jess code that evoked your function. For example, if the following Jess function calls were made,

Jess> (load-function ExMyUpcase)
Jess> (my-upcase foo)
"FOO"

the first argument to call() would be a ValueVector of length two. The first element would be a Value containing the symbol (type RU.SYMBOL) my-upcase, and the second argument would be a Value containing the string (RU.STRING) "foo".

Note that we use vv.get(1).stringValue(context) to get the first argument to my-upcase as a Java String. If the argument doesn't contain a string, or something convertible to a string, stringValue() will throw a JessException describing the problem; hence you don't need to worry about incorrect argument types if you don't want to. vv.get(0) will always return the symbol my-upcase, the name of the function being called (the clever programmer will note that this would let you construct multiple objects of the same class, implementing different functions based on the name of the function passed in as a constructor argument). If you want, you can check how many arguments your function was called with and throw a JessException if it was the wrong number by using the vv.size() method. In any case, our simple implementation extracts a single argument and uses the Java toUpperCase() method to do its work. call() must wrap its return value in a jess.Value object, specifying the type (here it is RU.STRING).
12.1.1.1. Legal return values
A Userfunction must return a valid jess.Value object; it cannot return tghe Java null value. To return "no value" to Jess, use nil. The value of nil is available in the public static final variable jess.Funcall.NIL.

12.1.2. Loading your Userfunction

Having written this class, you can then, in your Java main program, simply call Rete.addUserfunction() with an instance of your new class as an argument, and the function will be available from Jess code. So, we could have

import jess.*;
public class ExAddUF {
  public static void main(String[] argv) throws JessException {
    // Add the 'my-upcase' command to Jess
    Rete r = new Rete();
    r.addUserfunction(new ExMyUpcase());

    // This will print "FOO".
    r.eval("(printout t (my-upcase foo) crlf)");
  }
}
C:\> java ExAddUF
FOO

Alternatively, the Jess language command load-function could be used to load my-upcase from Jess:

Jess> (load-function ExMyUpcase)
Jess> (printout t (my-upcase foo) crlf)
FOO

12.1.3. Calling assert from a Userfunction

The jess.Rete.assertFact() method has two overloads: one version takes a jess.Context argument, and the other does not. When writing a Userfunction, you should always use the first version, passing the jess.Context argument to the Userfunction. If you do not, your Userfunction will not interact correctly with the logical conditional element.

12.2. Writing Extension Packages

The jess.Userpackage interface is a handy way to group a collection of Userfunctions together, so that you don't need to install them one by one (all of the extensions shipped with Jess are included in Userpackage classes). A Userpackage class should supply the one method add(), which should simply add a collection of Userfunctions to a Rete object using addUserfunction(). Nothing mysterious going on, but it's very convenient. As an example, suppose MyUpcase was only one of a number of similar functions you wrote. You could put them in a Userpackage class like this:

import jess.*;
public class ExMyStringFunctions implements Userpackage {
    public void add(Rete engine) {
        engine.addUserfunction(new ExMyUpcase());
        // Other similar statements
    }
}

Now in your Java code, you can call

import jess.*;
public class ExAddUP {
  public static void main(String[] argv) throws JessException {
    // Add the 'my-upcase' command to Jess
    Rete r = new Rete();
    r.addUserpackage(new ExMyStringFunctions());

    // This will still print FOO.
    r.eval("(printout t (my-upcase foo) crlf)");
  }
}
C:\> java ExAddUP
FOO

or from your Jess code, you can call

Jess> (load-package ExMyStringFunctions)
Jess> (printout t (my-upcase foo) crlf)
FOO

to load these functions in. After either of these snippets, Jess language code could call my-upcase, my-downcase, etc.

Userpackages are a great place to assemble a collection of interrelated functions which potentially can share data or maintain references to other function objects. You can also use Userpackages to make sure that your Userfunctions are constructed with the correct constructor arguments.

All of Jess's "built-in" functions are simply Userfunctions, albeit ones which have special access to Jess' innards. Most of them are automatically loaded by code in the jess.Funcall class. You can use these as examples for writing your own Jess extensions.

12.3. Obtaining References to Userfunction Objects

Occasionally it is useful to be able to obtain a reference to an installed Userfunction object. The method Userfunction Rete.findUserfunction(String name) lets you do this easily. It returns the Userfunction object registered under the given name, or null if there is none. This is useful when you write Userfunctions which themselves maintain state of some kind, and you need access to that state.