10. Introduction to Programming with Jess in Java

There are two main ways in which Java code can be used with Jess: Java can be used to extend Jess, and the Jess library can be used from Java. The material in this section is relevant to both of these endeavors. Refer to the API documentation for the complete story on these classes.
Note: the code samples herein are necessarily not complete Java programs. In general, all excerpted code would need to appear inside a try block, inside a Java method, inside a Java class, to compile; and all Java source files are expected to include the "import jess.*;" declaration. Sometimes examples build on previous ones; this is usually clear from context. Such compound examples will need to be assembled into one method before compiling.

10.1. The jess.Rete class

The jess.Rete class is the rule engine itself. Each jess.Rete object has its own working memory, agenda, rules, etc. To embed Jess in a Java application, you'll simply need to create one or more jess.Rete objects and manipulate them appropriately. We'll cover this in more detail in the section on embedding Jess in Java applications. Here I will cover some general features of the jess.Rete class.

10.1.1. Equivalents for common Jess functions

Several of the most commonly used Jess functions are wrappers for methods in the jess.Rete class. Examples are run(), run(int), reset(), clear(), assertFact(Fact), retract(Fact), retract(int), and halt(). You can call these from Java just as you would from Jess.

10.1.2. Executing other Jess commands

You can use the Rete class's jess.Rete.eval(java.lang.String) method to easily execute, from Java, any Jess function call or construct definition that can be represented as a parseable String. For example,

import jess.*;
public class ExSquare {
  public static void main(String[] unused) {
    try {
        Rete r = new Rete();
        r.eval("(deffunction square (?n) (return (* ?n ?n)))");
        Value v = r.eval("(square 3)");

        // Prints '9'
        System.out.println(v.intValue(r.getGlobalContext()));
    } catch (JessException ex) {
        System.err.println(ex);
    }
  }
}
C:\> java ExSquare
9
eval() returns the jess.Value object returned by the command. Commands executed via eval() may refer to Jess variables; they will be interpreted in the global context. In general, only defglobals can be used in this way.

Note that you may only pass one function call or construct at a time to eval().
10.1.2.1. Optional commands
Note that when you create a Rete object from Java, it will already contain definitions for all of the functions that come with Jess. There are no longer any "optional" commands.

10.1.3. The script library

Some of Jess's commands are defined in Jess language code, in the file jess/scriptlib.clp. Each Rete object will load this script library when it is created and again if (clear) is called. In previous versions of Jess you had to do this yourself; this is no longer necessary.

10.1.4. Methods for adding, finding and listing constructs

The easiest (and still encouraged) way to define templates, defglobals, and other constructs is to use Jess language code and let Jess parse the textual definition. However, many of these constructs are represented by public classes in the Jess library, and if you wish, you can construct your own instances of these in Java code and add them to an engine explicitly. This is currently possible for most, but not all, Jess constructs. Right now the jess.Defrule class does not expose enough public methods to properly create one outside of the jess package. This is deliberate, as this API is likely to change again in the near future. For information about the classes mentioned here (jess.Deftemplate, jess.Defglobal, etc) see the API documentation.

These jess.Rete methods let you add constructs to the engine:

These methods return individual constructs from within the engine, generally by name:

These methods return java.util.Iterators of various data structures in the engine:

Note that the utility class jess.FilteringIterator is very convenient for use together with these methods. Together with a jess.Filter implementation, this class lets you create an Iterator that silently discards objects that don't match some criteria. So, for example, you could use the built-in filter jess.Filter$ByModule to iterate over only the facts in a given module:


import jess.*;
import java.util.*;

public class ExFilter {
    public static void main(String[] argv) throws JessException {
        // Run a Jess program...
        Rete engine = new Rete();
        engine.batch("somecode.clp");
        
        // ... now retrieve only the facts in a module named RESULTS
        Iterator it = new FilteringIterator(engine.listFacts(),
                                            new Filter.ByModule("RESULTS"));
    }
}

10.1.5. I/O Routers

Several Jess functions like printout, format, read, and readline take an I/O router name as an argument, while other functions like open return an I/O router name. An I/O router name is just a symbolic name for a Java java.io.Writer and/or a java.io.Reader. Each jess.Rete instance keeps separate tables of input routers and output routers, so that both a Reader and a Writer can be registered under the same name in each rule engine. When you call, for example, printout, Jess uses the first argument to look up the appropriate Writer in that table, and that's where the output will go.

The most commonly used router is t, which is used as Jess' standard input and output. Jess also has a built-in router named WSTDOUT for printing user messages internally -- for example, the Jess> prompt and the output of commands like facts and ppdefrule. The read and readline commands take input from the t router by default. Output from the watch function goes to the WSTDOUT router by default, but you can make it go to any other router using the jess.Rete.setWatchRouter(java.lang.String) method.

As startup, Jess's standard routers are connected to Java's standard streams, so that output goes to the command-line window. This is perfect for command-line programs, but of course not acceptable for GUI-based applications. To remedy this, Jess lets you connect the t router (or any other router) to any Java java.io.Reader and java.io.Writer objects you choose. In fact, you can not only redirect the built-in routers, but you can add routers of your own, in much the same way that the open command creates a new router that reads from a file.

These functions in the Rete class let you manipulate the router list:

The words "input" and "output" are from the perspective of the Jess library itself; i.e., Jess reads from input routers and writes to output routers.

Note that you can use the same name for an input router and an output router (the t router is like that.) Note also that although these functions accept and return generic Reader and Writer objects, Jess internally uses java.io.PrintWriter and java.io.BufferedReader. If you pass in other types, Jess will construct one of these preferred classes to "wrap" the object you pass in.

When Jess starts up, there are three output routers and one input router defined: the t router, which reads and writes from the standard input and output; the WSTDOUT router, which Jess uses for all prompts, diagnostic outputs, and other displays; and the WSTDERR router, which Jess uses to print stack traces and error messages. By default, t is connected to System.in and System.out, and both WSTDOUT and WSTDERR are connected to System.out (neither is connected to System.err.) You can reroute these inputs and outputs simply by changing the Readers and Writers they are attached to using the above functions. You can use any kind of streams you can dream up: network streams, file streams, etc.

The boolean argument consoleLike to the addInputRouter method specifies whether the stream should be treated like the standard input or like a file. The difference is that on console-like streams, a read call consumes an entire line of input, but only the first token is returned; while on file-like streams, only the characters that make up each token are consumed on any one call. That means, for instance, that a read followed by a readline will consume two lines of text from a console-like stream, but only one from a file-like stream, given that the first line is of non-zero length.

The jess.Rete class has two more handy router-related methods: getOutStream() and getErrStream(), both of which return a java.io.PrintWriter object. getOutStream() returns a stream that goes to the same place as the current setting of WSTDOUT; getErrStream() does the same for WSTDERR.

10.1.6. TextAreaWriter, JTextAreaWriter and TextReader

Jess ships with three utility classes that can be very useful when building GUIs for Jess: the jess.awt.TextAreaWriter, jess.swing.JTextAreaWriter and jess.awt.TextReader classes. All three can serve as adapters between Jess and graphical input/output widgets. The TextAreaWriter class is, as the name implies, a Java java.io.Writer that sends any data written to it to a java.awt.TextArea. This lets you place Jess's output in a scrolling window on your GUI. The jess.Console and jess.ConsoleApplet Jess GUIs use these classes. To use TextAreaWriter simply call addOutputRouter(), passing in an instance of this class:

import java.awt.TextArea;
import jess.awt.*;
import jess.*;
public class ExTAW {
  public static void main(String[] unused) throws JessException {
     TextArea ta = new TextArea(20, 80);
     TextAreaWriter taw = new TextAreaWriter(ta);

     Rete r = new Rete();
     r.addOutputRouter("t", taw);
     r.addOutputRouter("WSTDOUT", taw);
     r.addOutputRouter("WSTDERR", taw);
     // Do something interesting, then...
     System.exit(0);
  }
}
C:\> java ExTAW
Now the output of the printout command, for example, will go into a scrolling window (of course, you need to display the TextArea on the screen somehow!) Study jess/ConsolePanel.java and jess/Console.java to see a complete example of this. JTextAreaWriter works the same way, but using a Swing javax.swing.JTextArea instead.

jess.awt.TextReader is similar, but it is a java.io.Reader instead. It is actually quite similar to java.io.StringReader, except that you can continually add new text to the end of the stream (using the appendText() method). It is intended that you create a jess.awt.TextReader, install it as an input router, and then (in an AWT event handler, somewhere) append new input to the stream whenever it becomes available. See the same jess/Console* files for a complete usage example for this class as well.

10.2. The jess.JessException class

The jess.JessException exception type is the only kind of exception thrown by any functions in the Jess library. jess.JessException is rather complex, as exception classes go. An instance of this class can contain a wealth of information about an error that occurred in Jess. Besides the typical error message, a jess.JessException may be able to tell you the name of the routine in which the error occurred, the name of the Jess constructs that were on the exceution stack, the relevant text and line number of the executing Jess language program, and the Java exception that triggered the error (if any.) See the the API documentation for details.

One of the most important pieces of advice for working with the Jess library is that in your catch clauses for JessException, display the exception object. Print it to System.out, or convert to a String and display it in a dialog box. The exceptions are there to help you by telling when something goes wrong; don't ignore them.

Another important tip: the JessException class has a method getCause which returns non-null when a particular JessException is a wrapper for another kind of exception. For example, if you use the Jess function call to call a function that throws an exception, then call will throw a JessException, and calling JessException.getCause() will return the real exception that was thrown. Your JessException handlers should always check getCause(); if your handler simply displays a thrown exception, then it should display the return value of getCause(), too. getCause() replaces the now deprecated getNextException().

10.3. The jess.Value class

The class jess.Value is probably the one you'll use the most in working with Jess. A Value is a self-describing data object. Every datum in Jess is contained in one. Once it is constructed, a Value's type and contents cannot be changed; it is immutable. Value supports a type() function, which returns one of these type constants (defined in the class jess.RU (RU = "Rete Utilities")):
final public static int NONE             =      0; // an empty value (not NIL)
final public static int SYMBOL           =      1; // a symbol
final public static int STRING           =      2; // a string
final public static int INTEGER          =      4; // an integer
final public static int VARIABLE         =      8; // a variable
final public static int FACT             =     16; // a jess.Fact object
final public static int FLOAT            =     32; // a double float
final public static int FUNCALL          =     64; // a function call
final public static int LIST             =    512; // a list
final public static int DESCRIPTOR       =   1024; // (internal use)
final public static int JAVA_OBJECT      =   2048; // a Java object
final public static int INTARRAY         =   4096; // (internal use)
final public static int MULTIVARIABLE    =   8192; // a multifield
final public static int SLOT             =  16384; // (internal use)
final public static int MULTISLOT        =  32768; // (internal use)
final public static int LONG             =  65536; // a Java long
final public static int LAMBDA           = 131072; // a lambda expression
Please always use the names, not the literal values, as the latter are subject to change without notice.

Value objects can be constructed by specifying the data and (usually) the type. Each overloaded constructor assures that the given data and the given type are compatible. Note that for each constructor, more than one value of the type parameter may be acceptable. The available constructors are:

public Value(Object o)
public Value(String s, int type) throws JessException
public Value(Value v)
public Value(ValueVector f, int type) throws JessException
public Value(double d, int type) throws JessException
public Value(int value, int type) throws JessException
public Value(boolean b)

10.3.1. ValueFactory

Alternatively, you can use the jess.ValueFactory class to obtain Value objects. This is a good idea because ValueFactory caches many of the values you obtain through it, leading to a sometimes drastic reduction in memory use due to sharing. You can get a ValueFactory object using the jess.Rete.getValueFactory() method:


import jess.*;
public class ExValueFactory {
  public static void main(String[] unused) throws JessException {
    Rete engine = new Rete();
    ValueFactory f = engine.getValueFactory();
    Value v1 = f.get("foo", RU.SYMBOL);
    Value v2 = f.get("foo", RU.SYMBOL);

    // Prints "true"
    System.out.println(v1 == v2);
  }
}
C:\> java ExValueFactory
true
Value supports a number of functions to get the actual data out of a Valueobject. These are
public Object javaObjectValue(Context c) throws JessException
public String stringValue(Context c) throws JessException
public String symbolValue(Context c) throws JessException
public Fact factValue(Context c) throws JessException
public Funcall funcallValue(Context c) throws JessException
public ValueVector listValue(Context c) throws JessException
public double floatValue(Context c) throws JessException
public double numericValue(Context c) throws JessException
public int intValue(Context c) throws JessException
public long longValue(Context c) throws JessException
The class jess.Context is described in the next section. If you try to convert random values by creating a Value and retrieving it as some other type, you'll generally get a JessException. However, some types can be freely interconverted: for example, integers and floats.

10.3.2. The subclasses of jess.Value

jess.Value has a number of subclasses: jess.Variable, jess.FuncallValue, jess.FactIDValue, and jess.LongValue are the four of most interest to the reader. When you wish to create a value to represent a variable, a function call, a fact, or a Java long, you must use the appropriate subclass.
Note to the design-minded: we should have used a Factory pattern here and hidden the subclasses from the programmer. This will be introduced in a future version of Jess.
10.3.2.1. The class jess.Variable
Use this subclass of Value when you want to create a Value that represents a Variable. The one constructor looks like this:
public Variable(String s, int type) throws JessException
The type must be RU.VARIABLE or RU.MULTIVARIABLE or an exception will be thrown. The String argument is the name of the variable, without any leading '?' or '$' characters.
10.3.2.2. The class jess.FuncallValue
Use this subclass of Value when you want to create a Value that represents a function call (for example, when you are creating a jess.Funcall containing nested function calls.) The one constructor looks like this:
public FuncallValue(Funcall f) throws JessException
10.3.2.3. The class jess.LongValue
Use this subclass of Value when you want to create a Value that represents a Java long. These are mostly used to pass to Java functions called via reflection. The one constructor looks like
public LongValue(long l) throws JessException
10.3.2.4. The class jess.FactIDValue
Use this subclass of Value when you want to create a Value that represents a fact-id. The one constructor looks like this:
public FactIDValue(Fact f) throws JessException
In previous versions of Jess, fact-id's were more like integers; now they are really references to facts. As such, a fact-id must represent a valid jess.Fact object. Call javaObjectValue(Context) to get the jess.Fact object, and call Fact.getFactId() to get the fact-id as an integer. This latter manipulation will now rarely, if ever, be necessary.

10.3.3. Value resolution

Some jess.Value objects may need to be resolved before use. To resolve a jess.Value means to interpret it in a particular context. jess.Value objects can represent both static values (symbols, numbers, strings) and dynamic ones (variables, function calls). It is the dynamic ones that obviously have to be interpreted in context.

All the jess.Value member functions, like intValue(), that accept a jess.Context as an argument are self-resolving; that is, if a jess.Value object represents a function call, the call will be executed in the given jess.Context, and the intValue() method will be called on the result. Therefore, you often don't need to worry about resolution as it is done automatically. There are several cases where you will, however.

10.4. The jess.Context class

jess.Context represents an execution context for the evaluation of function calls and the resolution of variables. There are very few public member functions in this class, and only a few of general importance.

You can use getVariable() and setVariable()to get and change the value of a variable from Java code, respectively.

The function getEngine() gives any Userfunction access to the Rete object in which it is executing.

When a Userfunction is called, a jess.Context argument is passed in as the final argument. You should pass this jess.Context to any jess.Value.(x)Value() calls that you make.

10.5. The jess.ValueVector class

The jess.ValueVector class is Jess's internal representation of a list, and therefore has a central role in programming with Jess in Java. The jess.ValueVector class itself is used to represent generic lists, while specialized subclasses are used as function calls (jess.Funcall), facts (jess.Fact), and templates (Deftemplate).

Working with ValueVector itself is simple. Its API is reminiscent of java.util.Vector. Like that class, it is a self-extending array: when new elements are added the ValueVector grows in size to accomodate them. Here is a bit of example Java code in which we create the Jess list (a b c). Note that the add() method has several overloaded forms that convert primitives into jess.Value objects. The overload used here automatically converts its argument into a Jess symbol.


import jess.*;
public class ExABC {
  public static void main(String[] unused) throws JessException {
    ValueVector vv = new ValueVector();
    vv.add("a");
    vv.add("b");
    vv.add("c");

    // Prints "(a b c)"
    System.out.println(vv.toStringWithParens());
  }
}
C:\> java ExABC
(a b c)

The add() function returns the ValueVector object itself, so that add() calls can be chained together for convenience:


import jess.*;
public class ExChain {
  public static void main(String[] unused) throws JessException {
    ValueVector vv = new ValueVector();
    vv.add("a").add("b").add("c");
    // Prints "(a b c)"
    System.out.println(vv.toStringWithParens());
  }
}
C:\> java ExChain
(a b c)
To pass a list from Java to Jess, you should enclose it in a jess.Value object of type RU.LIST.

10.6. The jess.Funcall class

jess.Funcall is a specialized subclass of ValueVector that represents a Jess function call. It contains the name of the function, an internal pointer to the actual jess.Userfunction object containing the function code, and the arguments to pass to the function.

You can call Jess functions using jess.Funcall if you prefer, rather than using jess.Rete.executeFunction(). This method has less overhead since there is no parsing to be done. This example calls Jess's "set-reset-globals" function:

import jess.*;
public class ExResetGlobals {
  public static void main(String[] unused) throws JessException {
    Rete r = new Rete();
    Context c = r.getGlobalContext();
    Funcall f = new Funcall("set-reset-globals", r);
    f.arg(Funcall.FALSE);
    Value result = f.execute(c);
    System.out.println(result);
  }
}
C:\> java ExResetGlobals
FALSE
The example shows several styles of using jess.Funcall. You can chain add() calls, but remember that add() returns ValueVector, so you can't call execute() on the return value of Funcall.add() A special method arg() is provided for this purpose; it does the same thing as add() but returns the Funcall as a Funcall.

The first entry in a Funcall's ValueVector is the name of the function, even though you don't explicitly set it. Changing the first entry will not automatically change the function the Funcall will call!

The Funcall class also contains some public static constant Value member objects that represent the special symbols nil, TRUE, FALSE, EOF, etc. You are encouraged to use these.

10.7. The jess.Fact class

Another interesting subclass of ValueVector is jess.Fact, which, predictably, is how Jess represents facts. A Fact is stored as a list in which all the entries correspond to slots. The head or name of the fact is stored in a separate variable (available via the getName() method.)

Once you assert a jess.Fact object, you no longer "own" it - it becomes part of the Rete object's internal data structures. As such, you must not change the values of any of the Fact's slots. If you retract the fact, the Fact object is released and you are free to alter it as you wish. Alternatively, you can use the jess.Rete.modify(jess.Fact, java.lang.String, jess.Value) method to modify a fact.

10.7.1. Constructing an Unordered Fact from Java

In the following example, we create a template and assert an unordered fact that uses it.

import jess.*;
public class ExPoint {
  public static void main(String[] unused) throws JessException {
    Rete r = new Rete();
    r.eval("(deftemplate point \"A 2D point\" (slot x) (slot y))");

    Fact f = new Fact("point", r);
    f.setSlotValue("x", new Value(37, RU.INTEGER));
    f.setSlotValue("y", new Value(49, RU.INTEGER));
    r.assertFact(f);

    r.eval("(facts)");
  }
}
C:\> java ExPoint
f-0   (MAIN::point (x 37) (y 49))
For a total of 1 facts in module MAIN.

10.7.2. Constructing a Multislot from Java

In this example, the template has a multislot. In Java, a multislot is represented by a Value of type RU.LIST; the Value object contains a ValueVector containing the fields of the multislot.

import jess.*;
public class ExMulti {
  public static void main(String[] unused) throws JessException {
    Rete r = new Rete();
    r.eval("(deftemplate vector \"A named vector\" (slot name) (multislot list))");

    Fact f = new Fact("vector", r);
    f.setSlotValue("name", new Value("Groceries", RU.SYMBOL));
    ValueVector vv = new ValueVector();
    vv.add(new Value("String Beans", RU.STRING));
    vv.add(new Value("Milk", RU.STRING));
    vv.add(new Value("Bread", RU.STRING));
    f.setSlotValue("list", new Value(vv, RU.LIST));
    r.assertFact(f);

    r.eval("(facts)");
  }
}
C:\> java ExMulti
f-0   (MAIN::vector (name Groceries) (list "String Beans" "Milk" "Bread"))
For a total of 1 facts in module MAIN.

10.7.3. Constructing an Ordered Fact from Java

An ordered fact is actually represented as an unordered fact with a single slot: a multislot named __data. You don't need to create a template for an ordered fact: one will be created automatically if it doesn't already exist.

import jess.*;
public class ExOrdered {
  public static void main(String[] unused) throws JessException {
    Rete r = new Rete();

    Fact f = new Fact("letters", r);
    ValueVector vv = new ValueVector();
    vv.add("a").add("b").add("c");
    f.setSlotValue("__data", new Value(vv, RU.LIST));
    r.assertFact(f);

    r.eval("(facts)");
  }
}
C:\> java ExOrdered
f-0   (MAIN::letters a b c)
For a total of 1 facts in module MAIN.

10.8. The jess.Deftemplate class

Yet another interesting subclass of ValueVector is jess.Deftemplate, the purpose of which should be obvious. Deftemplate has a fairly large interface which allows you to set and query the properties of a template's slots.

This example is an alternative to the deftemplate command in the previous example.

import jess.*;
public class ExBuildDeftemplate {
  public static void main(String[] unused) throws JessException {
    Rete r = new Rete();
    Deftemplate dt = new Deftemplate("point", "A 2D point", r);
    Value zero = new Value(0, RU.INTEGER);
    dt.addSlot("x", zero, "NUMBER");
    dt.addSlot("y", zero, "NUMBER");
    r.addDeftemplate(dt);

    // Now create and assert Fact
  }
}

10.9. Parsing Jess code with jess.Jesp

You can parse Jess language code directly with the class jess.Jesp. Simply loading the contents of a file (or any other data source that can be supplied as a java.io.Reader is very easy:

import jess.*;
import java.io.*;
public class ExReadInFile {
  public static void main(String[] unused) throws JessException, IOException {
    Rete engine = new Rete();
    FileReader file = new FileReader("myfile.clp");
    try {
      Jesp parser = new Jesp(file, engine);
      parser.parse(false);
    } finally {
      file.close();
    }
  }
}

But jess.Jesp's public interface is much richer than that. If you want to, you can parse the file one expression at a time, and have access to the parsed data. The method parseExpression returns java.lang.Object, and the returned object can either be a jess.Value or one of the Jess classes that represent a construct (jess.Defrule, jess.Deftemplate, etc.) In addition, you can choose to have the parser execute function calls as it parses them, or simply return them to you unexecuted (this is controlled by the second argument to parseExpression.

import jess.*;
import java.io.*;
public class ExParseExpressions {
  public static void main(String[] unused) throws JessException, IOException {
    Rete engine = new Rete();
    FileReader file = new FileReader("myfile.clp");
    Context context = engine.getGlobalContext();
    try {
      Jesp parser = new Jesp(file, engine);
      Object result = Funcall.TRUE;
      while (!result.equals(Funcall.EOF)) {
        result = parser.parseExpression(context, false);
        // Here you can use instanceof to determine what sort
        // of object "result" is, and process it however you want
      }
    } finally {
      file.close();
    }
  }
}

There are also methods in jess.Jesp to control whether comments should be returned from parseExpression or just skipped, methods to fetch various pieces of information about the parsing process, and a mechanism for controlling whether warnings or just errors should be reported.

10.10. The jess.Token class

The jess.Token class is used to represent partial matches in the Rete network. You'll use it if you're writing an Accelerator (not documented here) or perhaps if you're working with queries.

Only a few methods of jess.Token are public, and fewer are of use to the programmer. int size() tells you how many jess.Facts are in a given jess.Token. The most important method is Fact fact(int), which returns the jess.Fact objects that make up the partial match. Its argument is the zero-based index of the jess.Fact to retrieve, and must be between 0 and the return value of size(). Each Fact will correspond to one pattern on a rule or query LHS; dummy facts are inserted for not and test CEs.

10.11. The jess.JessEvent and jess.JessListener classes

jess.JessEvent and jess.JessListener make up Jess's rendition of the standard Java event pattern. By implementing the JessListener interface, a class can register itself with a source of JessEvents, like the jess.Rete class. jess.Rete (potentially) fires events at all critical junctures during its execution: when rules fire, when a reset() or clear() call is made, when a fact is asserted or retracted, etc. JessEvent has a getType() method to tell you what sort of event you have been notified of; the type will be one of the constants in the JessEvent class.

You can control which events a jess.Rete object will fire using the setEventMask() method. The argument is the result of logical-OR-ing together some of the constants in the jess.JessEvent class. By default, the event mask is 0 and no events are sent.

As an example, let's suppose you'd like your program's graphical interface to display a running count of the number of facts on the fact-list, and the name of the last executed rule. You've provided a static method, MyGUI.displayCurrentRule(String ruleName), which you would like to have called when a rule fires. You've got a pair of methods MyGUI.incrementFactCount() and MyGUI.decrementFactCount() to keep track of facts. And you've got one more static method, MyGUI.clearDisplay(), to call when Jess is cleared or reset. To accomplish this, you simply need to write an event handler, install it, and set the event mask properly. Your event handler class might look like this.


import jess.*;

public class ExMyEventHandler implements JessListener {
  public void eventHappened(JessEvent je) {
    int defaultMask = JessEvent.DEFRULE_FIRED | JessEvent.FACT |
                      JessEvent.RESET | JessEvent.CLEAR;
    int type = je.getType();
    switch (type) {
      case JessEvent.CLEAR:
      case JessEvent.RESET:
        // MyGUI.clearDisplay();
        break;

      case JessEvent.DEFRULE_FIRED:
        // MyGUI.displayCurrentRule( ((Activation) je.getObject()).getRule().getName());
        break;

      case JessEvent.FACT | JessEvent.REMOVED:
        // MyGUI.decrementFactCount();
        break;

      case JessEvent.FACT:
        // MyGUI.incrementFactCount();
        break;

      default:
        // ignore
    }
  }
}

Note how the event type constant for fact retracting is composed from FACT | REMOVED. In general, constants like DEFRULE, DEFTEMPLATE, etc, refer to the addition of a new construct, while composing these with REMOVE signifies the removal of the same construct.

The getObject() method returns ancillary data about the event. In general, it is an instance of the type of object the event refers to; for DEFRULE_FIRED it is a jess.Activation.

To install this listener, you would simply create an instance and call jess.Rete.addEventListener(), then set the event mask:

import jess.*;
public class ExMask {
  public static void main(String[] unused) throws JessException {
    Rete engine = new Rete();
    engine.addJessListener(new ExMyEventHandler());
    engine.setEventMask(engine.getEventMask() |
                        JessEvent.DEFRULE_FIRED | JessEvent.CLEAR |
                        JessEvent.FACT | JessEvent.RESET );
  }
}

Note that each event handler added will have a negative impact on Jess performance so their use should be limited.

There is no way to receive only one of an event / (event | REMOVE) pair.

10.11.1. Working with events from the Jess language

It's possible to work with the event classes from Jess language code as well. To write an event listener, you can use the jess.JessEventAdapter class. This class works rather like the jess.awt adapter classes do. Usage is best illustrated with an example. Let's say you want to print a message each time a new template is defined, and you want to do it from Jess code. Here it is:
Jess> ;; make code briefer
(import jess.*)
TRUE
Jess> ;; Here is the event-handling deffunction
;; It accepts one argument, a JessEvent
(deffunction display-deftemplate-from-event (?evt)
  (if (eq (JessEvent.DEFTEMPLATE) (get ?evt type)) then
      (printout t "New deftemplate: " (call (call ?evt getObject) getName) crlf)))
TRUE
Jess> ;; Here we install the above function using a JessEventAdapter
(call (engine) addJessListener
(new JessEventAdapter display-deftemplate-from-event (engine)))
Jess> ;; Now we add DEFTEMPLATE to the event mask
(set (engine) eventMask
(bit-or (get (engine) eventMask) (JessEvent.DEFTEMPLATE)))
Now whenever a new template is defined, a message will be displayed.

10.12. Setting and Reading Java Bean Properties

As mentioned previously, Java objects can be explicitly pattern-matched on the LHS of rules, but only to the extent that they are Java Beans. A Java Bean is really just a Java object that has a number of methods that obey a simple naming convention for Java Bean properties. A class has a Bean property if, for some string X and type T it has either or both of: Note that the capitalization is also important: for example, for a method named isVisible, the property's name is visible, with a lower-case V. Only the capitalization of the first letter of the name is important. You can conveniently set and get these properties using the Jess set and get methods. Note that many of the trivial changes in the Java 1.1 were directed towards making most visible properties of objects into Bean properties.

An example: AWT components have many Bean properties. One is visible, the property of being visible on the screen. We can read this property in two ways: either by explicitly calling the isVisible() method, or by querying the Bean property using get.
Jess> (defglobal ?*frame* = (new java.awt.Frame "Frame Demo"))
TRUE
Jess> ;; Directly call 'isVisible', or...
(printout t (call ?*frame* isVisible) crlf)
FALSE
Jess> ;; ... equivalently, query the Bean property
(printout t (get ?*frame* visible) crlf)
FALSE

10.13. Formatting Jess Constructs

The class jess.PrettyPrinter can produce a formatted rendering of many Jess objects, including jess.Defrules, Deffunctions jess.Defquerys, etc -- anything that implements the jess.Visitable interface.

jess.PrettyPrinter is very simple to use: you just create an instance, passing the object to be rendered as a constructor argument, and then call toString to get the formatted result.

import jess.*;
public class ExPretty {
  public static void main(String[] unused) throws JessException {
    Rete r = new Rete();
    r.eval("(defrule myrule (A) => (printout t \"A\" crlf))");
    Defrule dr = (Defrule) r.findDefrule("myrule");
    System.out.println(new PrettyPrinter(dr));
  }
}
C:\> java ExPretty

(defrule MAIN::myrule
  (A)
  =>
  (printout t "A" crlf))