8. Using Java from Jess
8.1. Java reflection
Jess includes a number of functions that let you create and manipulate Java objects directly from Jess. Using them, you can do virtually anything you can do from Java code, including 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. To do this, I use Jess's new and call functions:
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"
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"
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
Jess> (get-member System out)
<Java-Object:java.io.PrintStream>
8.1.1. Calling static methods
You can invoke static methods using call as well by specifying the name of the class as the first argument rather than a Java object. When you're calling a static method, "call" is not optional.
Jess> (call System gc)
There's a problem with using call with static methods, though: it can be ambiguous if a static method has the same name as an instance method of the java.lang.String class. In practice, this doesn't happen often, but when it does, it can be confusing. Therefore, in Jess 7, the preferred mechanism for invoking Java static methods is not to use call, but instead to use Jess's import function to import the method's class. This will automatically create a Jess function that invokes the given static method. For example:
Jess> (import java.util.Calendar)
Jess> (bind ?cal (Calendar.getInstance))
<Java-Object:java.util.GregorianCalendar>
8.1.2. Type conversion of arguments
Jess converts values from Java to Jess types according to the following table.Java type | Jess type |
---|---|
A null reference | The symbol 'nil' |
A void return value | The symbol 'nil' |
String | RU.STRING |
An array | A Jess list |
boolean or java.lang.Boolean | The symbols 'TRUE' and 'FALSE' |
byte, short, int, or their wrappers | RU.INTEGER |
long or Long | RU.LONG |
double, float or their wrappers | RU.FLOAT |
char or java.lang.Character | RU.SYMBOL |
anything else | RU.JAVA_OBJECT |
Jess type | Possible Java types |
---|---|
RU.JAVA_OBJECT | The wrapped object |
The symbol 'nil' | A null reference |
The symbols 'TRUE' or 'FALSE' | java.lang.Boolean or boolean |
RU.SYMBOL, RU.STRING | String, char, java.lang.Character |
RU.FLOAT | float, double, and their wrappers |
RU.INTEGER | long, short, int, byte, char, and their wrappers |
RU.LONG | long, short, int, byte, char, and their wrappers |
RU.LIST | A Java array |
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();
(store <name> <value>) (fetch <name>) (clear-storage)
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>)
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 java.lang.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 Maps and 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-value-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:- A method named getX which returns T and accepts no arguments; or, if T is boolean, named isX which accepts no arguments;
- A method named setX which returns void and accepts a single argument of type T.
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