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.
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 getNextException 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.getNextException() will return the real exception that was thrown. Your JessException handlers should always check getNextException(); if your handler simply displays a thrown exception, then it should display the return value of getNextException(), too.
final public static int NONE = 0; ; an empty value (not NIL) final public static int ATOM = 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 fact index 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 multifield final public static int DESCRIPTOR = 1024; ; (internal use) final public static int EXTERNAL_ADDRESS = 2048; ; a Java object final public static int INTARRAY = 4096; ; (internal use) final public static int MULTIVARIABLE = 8192; ; a multivariable final public static int SLOT = 16384; ; (internal use) final public static int MULTISLOT = 32768; ; (internal use) final public static int LONG = 65536; ; a Java longPlease always use the names, not the literal values, and the latter are subject to change without notice.
Value objects are 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) throws JessException 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 JessExceptionValue supports a number of functions to get the actual data out of a Valueobject. These are
public Object externalAddressValue(Context c) throws JessException public String stringValue(Context c) throws JessException public ValueVector factValue(Context c) throws JessException public ValueVector 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 descriptorValue(Context c) throws JessException public int intValue(Context c) throws JessExceptionThe 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.
Note to the design-minded: I could have use a Factory pattern here and hidden the subclasses from the programmer. Because of the many different Value constructors, and for performance reasons, I decided this wouldn't be worth the overhead.
public Variable(String s, int type) throws JessExceptionThe 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.
public FuncallValue(Funcall f) throws JessException
public LongValue(long l) throws JessException
public FactIDValue(Fact f) throws JessExceptionIn 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 externalAddressValue(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.
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.
Note that arguments to deffunctions are resolved automatically, before your Jess language code runs.
You can use getVariable() and setvariableto 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.
import jess.*; public class ExSquare { public static void main(String[] unused) { try { Rete r = new Rete(); r.executeCommand("(deffunction square (?n) (return (* ?n ?n)))"); Value v = r.executeCommand("(square 3)"); // Prints '9' System.out.println(v.intValue(r.getGlobalContext())); } catch (JessException ex) { System.err.println(ex); } } } C:\> java ExSquare 9executeCommand() returns the jess.Value object returned by the command. Commands executed via executeCommand() 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 executeCommand().
These methods are available in the class jess.Rete:
public Value store(String name, Value val); public Value store(String name, Object val); public Value fetch(String name); public void clearStorage();while these functions are available in Jess:
(store <name> <value>) (fetch <name>) (clear-storage)Both store methods accept a "name" and a value (in Java, either in the form of a jess.Value object or an ordinary Java object; in Jess, any value), returning any old value with that name, or null (or nil in Jess) if there is none. Both fetch methods accept a name, and return any value stored under that name, or null/nil if there is no such object. These functions therefore let you transfer data between Jess and Java that cannot be represented textually (of course they work for Strings, too.) In this example we create an object in Java, then pass it to Jess to be used as an argument to the definstance command.
import jess.*; public class ExFetch { public static void main(String[] unused) throws JessException { Rete r = new Rete(); r.store("DIMENSION", new java.awt.Dimension(10, 10)); r.executeCommand("(defclass dimension java.awt.Dimension)"); r.executeCommand("(definstance dimension (fetch DIMENSION) static)"); r.executeCommand("(facts)"); } } C:\> java ExFetch f-0 (MAIN::dimension (class <External-Address:java.lang.Class>) (height 10.0) (size <External-Address:java.awt.Dimension>) (width 10.0) (OBJECT <External-Address:java.awt.Dimension>)) For a total of 1 facts.
Note that storing a null (or nil) value will result in the given name being removed from the hashtable altogether. clearStorage() and clear-storage each remove all data from the hashtable.
Note that the Jess clear and Java clear() functions will call clearStorage(), but reset and reset() will not. Stored data is thus available across calls to reset().
Another example, with a main program in Java and a set of rules that return a result using store is in the directory Jess60/jess/examples/xfer/ .
These jess.Rete methods let you add constructs to the engine:
By default, 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 to any Java java.io.Reader and java.io.Writer objects you choose. In fact, you can not only redirect the t router, 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:
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. This odd behaviour is actually just following the behaviour of CLIPS.
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; errStream() does the same for WSTDERR.
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 ExTAWNow 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.
jess.awt.TextReader is similar, but it is a 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.
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).
import jess.*; public class ExABC { public static void main(String[] unused) throws JessException { ValueVector vv = new ValueVector(); vv.add(new Value("a", RU.ATOM)); vv.add(new Value("b", RU.ATOM)); vv.add(new Value("c", RU.ATOM)); // 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(new Value("a", RU.ATOM)).add(new Value("b", RU.ATOM)).add(new Value("c", RU.ATOM)); // 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.
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 is an alternate version of the "defclass Dimension" example above.
import java.awt.Dimension; import jess.*; public class ExADimension { public static void main(String[] unused) throws JessException { Rete r = new Rete(); Context c = r.getGlobalContext(); Value dimension = new Value("dimension", RU.ATOM); Funcall f = new Funcall("defclass", r); f.arg(dimension).arg(new Value("java.awt.Dimension", RU.ATOM)); f.execute(c); new Funcall("definstance", r).arg(dimension). arg(new Value(new Dimension(10, 10))). arg(new Value("static", RU.ATOM)).execute(c); new Funcall("facts", r).execute(c); } } C:\> java ExADimension f-0 (MAIN::dimension (class <External-Address:java.lang.Class>) (height 10.0) (size <External-Address:java.awt.Dimension>) (width 10.0) (OBJECT <External-Address:java.awt.Dimension>)) For a total of 1 facts.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 atoms nil, TRUE, FALSE, EOF, etc. You are encouraged to use these.
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.
import jess.*; public class ExPoint { public static void main(String[] unused) throws JessException { Rete r = new Rete(); r.executeCommand("(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.executeCommand("(facts)"); } } C:\> java ExPoint f-0 (MAIN::point (x 37) (y 49)) For a total of 1 facts.
import jess.*; public class ExMulti { public static void main(String[] unused) throws JessException { Rete r = new Rete(); r.executeCommand("(deftemplate vector \"A named vector\"" + " (slot name) (multislot list))"); Fact f = new Fact("vector", r); f.setSlotValue("name", new Value("Groceries", RU.ATOM)); 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.executeCommand("(facts)"); } } C:\> java ExMulti f-0 (MAIN::vector (name Groceries) (list "String Beans" "Milk" "Bread")) For a total of 1 facts.
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(new Value("a", RU.ATOM)); vv.add(new Value("b", RU.ATOM)); vv.add(new Value("c", RU.ATOM)); f.setSlotValue("__data", new Value(vv, RU.LIST)); r.assertFact(f); r.executeCommand("(facts)"); } } C:\> java ExOrdered f-0 (MAIN::letters a b c) For a total of 1 facts.
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 } } C:\> java ExBuildDeftemplate
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.
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: Rete engine = (Rete) je.getSource(); int mask = engine.getEventMask(); mask |= defaultMask; engine.setEventMask(mask); // FALL THROUGH 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 } } } C:\> java ExMyEventHandlerNote 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.Defrule.
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 ); } } C:\> java ExMaskOne added wrinkle: note how the handler for JessEvent.CLEAR sets the event mask. When (clear) is called, the event mask is typically reset to the default. When event handlers are called, they have the opportunity to alter the mask to re-install themselves. Alternatively, they can call removeJessListener() to unregister themselves.
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.
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 (get-member 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) (get-member JessEvent DEFTEMPLATE)))Now whenever a new template is defined, a message will be displayed.
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
import jess.*; public class ExPretty { public static void main(String[] unused) throws JessException { Rete r = new Rete(); r.executeCommand("(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 (MAIN::A) => (printout t "A" crlf))