8. Using Java from Jess

8.1. Java reflection

Among the list of functions above are a set that let you create and manipulate Java objects directly from Jess. Using them, you can do virtually anything you can do from Java code, except for defining new classes. Here is an example in which I create a Java HashMap and add a few String objects to it, then lookup one object and display it.
Jess> (bind ?ht (new java.util.HashMap))
<Java-Object:java.util.HashMap>
Jess> (call ?ht put "key1" "element1")

Jess> (call ?ht put "key2" "element2")

Jess> (call ?ht get "key1")
"element1"
As you can see, Jess converts freely between Java and Jess types when it can. Java objects that can't be represented as a Jess type are called Java object values. The HashMap in the example above is one of these.

Most of the time you can omit the call, leading to a notation a little more like Java code -- i.e.,
Jess> (bind ?ht (new java.util.HashMap))
<Java-Object:java.util.HashMap>
Jess> (?ht put "key1" "element1")

Jess> (?ht put "key2" "element2")

Jess> (?ht get "key1")
"element1"
The only time that call is absolutely required is when you're calling a static method.

Jess can also access member variables of Java objects using the set-member and get-member functions.
Jess> (bind ?pt (new java.awt.Point))
<Java-Object:java.awt.Point>
Jess> (set-member ?pt x 37)
37
Jess> (set-member ?pt y 42)
42
Jess> (get-member ?pt x)
37
You can access static members by using the name of the class instead of an object as the first argument to these functions.
Jess> (get-member System out)
<Java-Object:java.io.PrintStream>
Note that we don't have to say "java.lang.System". The java.lang package is implicitly "imported" much as it is in Java code. Jess also has an import function that you can use explicitly.

Jess converts values from Java to Jess types according to the following table.

Java typeJess type
A null referenceThe symbol 'nil'
A void return valueThe symbol 'nil'
StringRU.STRING
An arrayA Jess list
boolean or java.lang.BooleanThe symbols 'TRUE' and 'FALSE'
byte, short, int, or their wrappersRU.INTEGER
long or LongRU.LONG
double, float or their wrappersRU.FLOAT
char or java.lang.CharacterRU.SYMBOL
anything elseRU.JAVA_OBJECT

Jess converts values from Jess to Java types with some flexibility, according to this table. Generally when converting in this direction, Jess has some idea of a target type; i.e., Jess has a java.lang.Class object and a jess.Value object, and wants to turn the Value's contents into something assignable to the type named by the Class. Hence the symbol 'TRUE' could be passed to a function expecting a boolean argument, or to one expecting a String argument, and the call would succeed in both cases.

Jess typePossible Java types
RU.JAVA_OBJECTThe wrapped object
The symbol 'nil'A null reference
The symbols 'TRUE' or 'FALSE'java.lang.Boolean or boolean
RU.SYMBOL, RU.STRINGString, char, java.lang.Character
RU.FLOATfloat, double, and their wrappers
RU.INTEGERlong, short, int, byte, char, and their wrappers
RU.LONGlong, short, int, byte, char, and their wrappers
RU.LISTA Java array

Sometimes you might have trouble calling overloaded methods - for example, passing the String "TRUE" to a Java method that is overloaded to take either a boolean or a String. In this case, you can always resort to using an explicit wrapper class - in this case, passing a java.lang.Boolean object should fix the problem.

To learn more about the syntax of call, new, set-member, get-member, and other Java integration functions, see the Jess function guide.

8.2. Transferring values between Jess and Java code

This section describes a very easy-to-use mechanism for communicating inputs and results between Jess and Java code.

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 list 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.eval("(bind ?list (list dimension (fetch DIMENSION)))");
    r.eval("(printout t ?list)");
  }
}
C:\> java ExFetch
(dimension <Java-Object:java.awt.Dimension>)

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().

8.3. Implementing Java interfaces with Jess

Many Java libraries expect you to use callbacks. A callback is an object that implements a specific interface; you pass it to a library method, and the methods of the callback are invoked at some expected time. GUIs work this way -- the callbacks are called event handlers. But Java Threads work this way too -- a Runnable is a callback that] gets invoked on a new thread. So being able to implement an interface is an important part of programming in Java.

Jess supports creating callbacks with the implement function. This function is simple to use: you tell it the name of an interface, and the name of a deffunction, and it returns an object that implements that interface by calling that function. When any method of that interface is invoked, the deffunction will be called. The first argument will be the name of the interface function, and all the arguments of the interface function will follow.

As an example, here is an implementation of java.util.Comparator which sorts Strings in case-insensitive order:

Jess> (import java.util.Comparator)
Jess> (deffunction compare(?name ?s1 ?s2)
    (return ((?s1 toUpperCase) compareTo (?s2 toUpperCase))))
TRUE
Jess> (bind ?c (implement Comparator using compare))

8.3.1. Lambda expressions

There's a nice shortcut you can use when calling implement. Instead of defining a separate deffunction, you can define one in-line using the lambda facility. The lambda function lets you define a deffunction without naming it, and without adding it to a Rete engine. We can redo the example above using lambda like this:

Jess> (import java.util.Comparator)
Jess> (bind ?c (implement Comparator using (lambda (?name ?s1 ?s2)
    (return ((?s1 toUpperCase) compareTo (?s2 toUpperCase))))))

8.4. Java Objects in working memory

You can let Jess pattern-match on Java objects using definstance. You can also easily put Java objects into the slots of other Jess facts, as described elsewhere in this document. This section describes some minimal requirements for objects used in either of these ways.

Jess may call the equals and hashCode methods of any objects in working memory. As such, it is very important that these methods be implemented properly. The Java API documentation lists some important properties of equals and hashCode, but I will reiterate the most important (and most often overlooked) one here: if you write equals, you probably must write hashCode too. For any pair of instances of a class for which equals returns true, hashCode must return the same value for both instances. If this rule is not observed, Jess will appear to malfunction when processing facts containing these improperly defined objects in their slots. In particular, rules that should fire may not do so.

A value object is an instance of a class that represents a specific value. They are often immutable like Integer, Double, and String. For Jess's purposes, a value object is one whose hashCode() method returns a constant -- i.e., whose hash code doesn't change during normal operation of the class. Integer, Double, and all the other wrapper classes qualify, as does String, and generally all immutable classes. Any class that doesn't override the default hashCode() method also qualifies as a value object by the definition. Java's Collection classes (Lists, Maps, Sets, etc.) are classic examples of classes that are not value objects, because their hash codes depend on the collection's contents.

As far as Jess is concerned, an object is a value object as long as its hash code won't change while the object is in working memory. This includes the case where the object is contained in a slot of any fact. If the hash code will only change during calls to modify, then the object is still a value object.

Jess can make certain assumptions about value objects that lead to large performance increases during pattern matching. Because many classes are actually value classes by Jess's broad definition, Jess now assumes that all objects (except for Collections) are value objects by default. If you're working with a class that is not a value class, it's very important that you tell Jess about it by using the set-nonvalue-class function. Failure to do so will lead to undefined (bad) behavior.

8.5. 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