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. Definstance facts

You may have noticed that unordered facts look a bit like Java objects, or specifically, like Java Beans. The similarity is that both have a list of slots (for Java Beans, they're called properties) which contains values that might change over time. Jess has a mechanism for automatically generating templates that represent specific types of Java Beans. Jess can then use these templates to store a representation of a Java Bean's properties in the working memory. The working memory representation of the Bean can be static (changing infrequently, like a snapshot of the properties at one point in time) or dynamic (changing automatically whenever the Bean's properties change.) The Jess commands that make this possible are defclass and definstance. defclass tells Jess to generate a special template to represent a category of Beans, while definstance puts a representation of one specific Bean into working memory.

An example will probably help at this point. Let's say you have the following Java Bean class

import java.io.Serializable;
public class ExampleBean implements Serializable {
      private String name = "Bob";
      public String getName() { return name; }
      public void setName(String s) { name = s; }
}

This Bean has one property called "name". Before we can insert any of these Beans into the working memory, we need a template to represent them: we must use defclass to tell Jess to generate it:

Jess> (defclass simple ExampleBean)
ExampleBean
Jess> (ppdeftemplate simple)  
"(deftemplate MAIN::simple
 \"$JAVA-OBJECT$ ExampleBean\"
 (declare (from-class ExampleBean))
 (slot class)
 (slot name)
 (slot OBJECT))"

This is a strange looking template, but it does have a slot called "name", as we'd expect, that arises from the "name" property of our Bean. The slot "class" comes from the method getClass() that every object inherits from java.lang.Object, while the slot OBJECT is added by Jess; its value is always a reference to the Bean itself. See how the first argument to defclass is used as the template name.
Note that if you want your Java Beans to work with Jess's bload and bsave commands, the individual classes need to implement the java.io.Serializable tag interface.
Now let's say we want an actual ExampleBean in our working memory. Here we'll create one from Jess code, but it could come from anywhere. We will use the definstance function to add the object to the working memory.

Jess> (bind ?sb (new ExampleBean))
<Java-Object:ExampleBean>
Jess> (definstance simple ?sb static)
<Fact-0>
Jess> (facts)
f-0   (MAIN::simple (class <Java-Object:java.lang.Class>)
                    (name "Bob")
                    (OBJECT <Java-Object:ExampleBean>))
For a total of 1 facts in module MAIN.

As soon as we issue the definstance command, a fact representing the Bean appears in the working memory.

Now watch what happens if we change the "name" property of our Bean.

Jess> (call ?sb setName "Fred")
Jess> (facts)
f-0   (MAIN::simple (class <Java-Object:java.lang.Class>)
                    (name "Bob")
                    (OBJECT <Java-Object:ExampleBean>))
For a total of 1 facts in module MAIN.

Hmmm. The working memory still thinks our Bean's name is "Bob", even though we changed it to "Fred". What happens if we issue a reset command?

Jess> (reset)
TRUE
Jess> (facts)
f-0   (MAIN::initial-fact)
f-1   (MAIN::simple (class <Java-Object:java.lang.Class>)
                    (name "Fred")
                    (OBJECT <Java-Object:ExampleBean>))
For a total of 2 facts in module MAIN.

reset updates the definstance facts in the working memory to match their Java Beans. This behaviour is what you get when (as we did here) you specify static in the definstance command. Static definstances are refreshed only when a reset is issued.

If your objects will be changed often from outside of Jess, then it would be nice to have a better way to do this. If you want to have your definstance facts stay continuously up to date, Jess needs to be notified whenever a Bean property changes. For this to happen, the Bean has to support the use of java.beans.PropertyChangeListeners. For Beans that fulfill this requirement Jess will automatically arrange for working memory to be updated every time a property of the Bean changes. A simple JavaBean that correctly implements this interface looks like this:

            
import java.io.Serializable;
import java.beans.*;

public class ExampleAutomaticBean implements Serializable {
    private PropertyChangeSupport pcs = new PropertyChangeSupport(this);
    private String name = "Bob";

    public String getName() { return name; }
    public void setName(String s) {
        String temp = name;
        name = s;
        pcs.firePropertyChange("name", temp, s);
    }

    public void addPropertyChangeListener(PropertyChangeListener pcl) {
        pcs.addPropertyChangeListener(pcl);
    }
    public void removePropertyChangeListener(PropertyChangeListener pcl) {
        pcs.removePropertyChangeListener(pcl);
    }
}

        

You can also actively tell Jess to refresh its knowledge of the properties of individual objects using the jess.Rete.updateObject() method. This is useful if you want to update objects manually or if you want to use your own automated change mechanism instead of PropertyChangeEvents.

defclasses, like deftemplates, can extend one another. In fact, deftemplates can extend defclasses, and defclasses can extend deftemplates. Of course, for a defclass to extend a deftemplate, the corresponding Bean class must have property names that match the template's slot names. Note, also, that just because two Java classes have an inheritance relationship doesn't mean that if both are defclassed the two defclasses will. You must explicitly declare all such relationships using extends. See the full documenation for defclass for details.

Instead of using the defclass function to create templates from Java classes, you can accomplish the same thing using the special from-class declaration in a deftemplate construct. This provides a different syntax for defining defclasses, and more importantly, lets you apply "slot-specific" to defclasses. The syntax looks like:

(deftemplate simple
  (declare (from-class ExampleBean)
           (slot-specific TRUE)))
        

This is equivalent to calling (defclass simple ExampleBean) , plus it adds the slot-specific property.

One final note about Java Beans used with Jess: Beans are often operating in a multithreaded environment, and so it's important to protect their data with synchronized blocks or synchronized methods. However, sending PropertyChangeEvents while holding a lock on the Bean itself can be dangerous, as the Java Beans Specification points out:

"In order to reduce the risk of deadlocks, we strongly recommend that event sources should avoid holding their own internal locks when they call event listener methods. Specifically, as in the example code in Section 6.5.1, we recommend they should avoid using a synchronized method to fire an event and should instead merely use a synchronized block to locate the target listeners and then call the event listeners from unsynchronized code." -- JavaBean Specification, v 1.0.1, p.31.
Failing to heed this advice can indeed cause deadlocks in Jess.

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

Another example, with a main program in Java and a set of rules that return a result using store is in the directory Jess70b7/jess/examples/xfer/ .

8.4. 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.4.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.5. 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 or the static method jess.HashCodeComputer.setNonValueClass(). Failure to do so will lead to undefined (bad) behavior.

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