4. 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.
4.1. 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.
4.2. 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. Valuesupports 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 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_ID = 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 FACT = 256; ; a fact
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)
Please 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 JessException
Value 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 factIDValue(Context c) throws JessException
public int intValue(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.
4.2.1. The subclasses of jess.Value
jess.Value has a number of subclasses:
jess.Variable and jess.FuncallValue are the two of
interest to the reader. When you wish to create a value to represent a
variable or a function call, you must use the appropriate subclass.
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.
4.2.1.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.
4.2.1.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
4.2.2. 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 (atoms, 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.
-
When interpreting arguments to a function written in Java.
The parameters passed to a Java Userfunction may themselves represent
function calls. It may be important, therefore, that these values be
resolved only once, as these functions may have side-effects (I'm
tempted to use the computer-science word: these functions may not be
idempotent. Idempotent functions have no side-effects and thus
may be called multiple times without harm.) You can accomplish this by
calling one of the (x)Value() methods and storing the return
value, using this return value instead of the parameter
itseld. Alternatively, you may call resolveValue()and store
the return value in a new jess.Value variable, using this
value as the new parameter. Note that the type() method will
return RU.VARIABLE for a jess.Value object that
refers to a variable, regardless of the type of the value the variable
is bound to. The resolved value will return the proper type.
Note that arguments to deffunctions are resolved
automatically, before your Jess language code runs.
-
when returning a jess.Value object from a function
written in Java. If you return one of a function's parameters from
a Java Userfunction, be sure to return the return value of
resolveValue(), not the parameter itself.
-
When storing a jess.Value object. It is important
that any values passed out of a particular execution context be
resolved; for example, before storing a Value object in a Hashtable,
resolveValue() should always be called on both the key and
object.
4.3. 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 two of
general importance: you can use getVariable() and
setvariableto get and change the value of a variable from
Java code, respectively.
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.
4.4. The jess.Rete class
The jess.Rete class is the rule engine itself. Each
jess.Rete object has its own knowledge base, agenda, rules,
etc. To embed Jess in a Java application, you'll simply need to crate
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.
4.4.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(),
assert(Fact), retract(Fact), retract(int),
and halt(). You can call these from Java just as you
would from Jess.
4.4.2. Executing other Jess commands
You can use the Rete class's
executeCommand(String cmd) method to easily execute, from
Java, any Jess function call or construct definition that can be
represented as a parseable String. For example,
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());
}
catch (JessException ex)
{
System.err.println(ex);
}
executeCommand() 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().
4.4.3. The script library
Some of Jess's commands are defined in Jess language code, in the file
jess/scriptlib.clp. The Jess command line and graphical
console applications both load this script library when they start
up. Many of your applications will want to do so as well. See the
section on
embedding Jess in Java applications for
more details. For now, all you need to know is that it's a good idea
to include the line
r.executeCommand("(batch jess/scriptlib.clp)");
immediately after constructing a jess.Rete object.
4.4.4. 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. In this example we
create an object in Java, then pass it to Jess to be used as an
argument to the definstance command.
try
{
Rete r = new Rete();
// The "facts" command is in the script library
r.executeCommand("(batch jess/scriptlib.clp)");
r.store("BUTTON", new java.awt.Button("Press Me!"));
r.executeCommand("(defclass button java.awt.Button)");
r.executeCommand("(definstance button (fetch BUTTON) static)");
// Prints
// f-0 (button (actionCommand "Press Me!")
// (background nil) (enabled TRUE) (font nil)
// (foreground nil) (label "Press Me!") (name "button0")
// (visible TRUE) (OBJECT <External-Address:java.awt.Button>))
// For a total of 1 facts.
r.executeCommand("(facts)");
}
catch (JessException ex)
{
System.err.println(ex);
}
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
Jess50b1/jess/examples/xfer/ .
4.4.5. Methods for adding, finding and listing constructs
The easiest (and still encouraged) way to define
deftemplates, 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. I hope that by the time Jess 5.0 is
in final release, it will be possible to create Defrules from
Java code. 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:
- public void addDeffacts(Deffacts)
- public void addDefglobal(Defglobal)
- public void addDefrule(Defrule)
- public void addDeftemplate(Deftemplate)
- public void addUserfunction(Userfunction)
- public void addUserpackage(Userpackage)
These methods return individual constructs from within the engine,
generally by name:
- public Defglobal findDefglobal(String)
- public Defrule findDefrule(String)
- public Deftemplate findDeftemplate(String)
- public Userfunction findUserfunction(String)
These methods return java.util.Enumerations of various data
structures in the engine:
- public Enumeration listActivations()
- public Enumeration listDeffacts()
- public Enumeration listDefglobals()
- public Enumeration listDefrules()
- public Enumeration listDeftemplates()
- public Enumeration listFacts()
- public Enumeration listFunctions()
4.4.6. I/O Routers
The functions printout and format take an I/O
routter name as an argument. The default argument t is
Jess' standard input and output. Jess also has a special
WSTDOUT router for printing user messages internally - for example,
the Jess> prompt, the messages you get when you issue a watch
command, and the output of commands like facts and
ppdefrule. The read command and
readline take input from the t router by default.
By default, Jess's standard routerst 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 sam,e 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:
- public void addInputRouter(String s, Reader is, boolean consoleLike)
- public void addOutputRouter(String s, Writer os)
- public Reader getInputMode(String s)
- public Reader getInputRouter(String s)
- public Writer getOutputRouter(String s)
- public void removeInputRouter(String s)
- public void removeOutputRouter(String s)
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 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. 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.
4.4.7. The jess.awt.TextAreaWriter and jess.awt.TextReader classes
Jess ships with two utility classes that can be very useful when building
GUIs for Jess: the jess.awt.TextAreaWriter and
jess.awt.TextReader classes. Both can serve as adapters
between Jess and graphical input/output widgets. The TextAreaWriter
class is, as the name implies, a Java 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 jess.awt.TextAreaWriter;
// ...
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);
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.
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.
4.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 (multifields), while specialized subclasses
are used as function calls (jess.Funcall), facts
(jess.Fact), and deftemplates (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).
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());
The add() function returns the ValueVector object
itself, so that add() calls can be chained together for convenience:
vv.add(new Value("a", RU.ATOM)).add(new Value("b", RU.ATOM)).add(new Value("c", RU.ATOM));
To pass a list from Java to Jess, you should enclose it in a
jess.Value object of type RU.LIST.
4.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
is an alternate version of the "defclass Button" example above.
import java.awt.Button;
// ...
Rete r = new Rete();
Context c = r.getGlobalContext();
Value buttton = new Value("button", RU.ATOM);
Funcall f = new Funcall("batch", r);
f.add(new Value("jess/scriptlib.clp", RU.STRING));
f.execute(c);
f = new Funcall("defclass");
f.add(button).add(new Value("java.awt.Button", RU.ATOM));
f.execute(c);
new Funcall("definstance").arg(button).arg(new Value(new Button("Press Me"))).execute(c);
new Funcall("facts").execute(c);
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 cleass 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.
4.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.)
Recall that an ordered fact is actually represented as an unordered
fact with one multislot named __data.
In the following example, we create a deftemplate and assert a fact
that uses it.
Rete r = new Rete();
r.executeCommand("(batch jess/scriptlib.clp)");
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.assert(f);
// Prints f-0 (point (x 37) (y 49))
// For a total of 1 facts.
r.executeCommand("(facts)");
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.
4.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.
Deftemplate dt = new Deftemplate("point", "A 2D point");
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
4.9. 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 if you're
working with defqueries.
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.
4.10. 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. Note that regardless of the setting of
the event mask, JessEvent.RESET and JessEvent.CLEAR
events will always be sent and cannot be blocked.
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 MyEventHandler implements JessListener
{
public void eventHappened(JessEvent je)
{
int type = je.getType();
switch (type)
{
case JessEvent.CLEAR:
Rete engine = (Rete) je.getSource();
int mask = engine.getEventMask();
mask |= (JessEvent.DEFRULE_FIRED | JessEvent.FACT);
engine.setEventMask(mask);
// FALL THROUGH
case JessEvent.RESET:
MyGUI.clearDisplay(); break;
case JessEvent.DEFRULE_FIRED:
MyGUI.displayCurrentRule( ((Defrule) je.getObject()).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.Defrule.
To install this listener, you would simply create an instance and call
jess.Rete.addEventListener(), then set the event mask:
Rete engine = ...;
engine.addJessListener(new MyEventHandler());
engine.setEventMask(engine.getEventMask() |
JessEvent.DEFRULE_FIRED | JessEvent.FACT);
One 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.
4.10.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
deftemplate is defined, and you want to do it from Jess code. Here it
is:
;; make code briefer
(import 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)))
;; Here we install the above function using a JessEventAdapter
(call (engine) addJessListener
(new JessEventAdapter display-deftemplate-from-event (engine)))
;; Now we add DEFTEMPLATE to the event mask
(set (engine) eventMask
(bit-or (get (engine) eventMask) (get-member JessEvent DEFTEMPLATE)))
Now whenever a new deftemplate is defined, a message will be
displayed.
4.11. 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.
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.
(defglobal ?*frame* (new java.awt.Frame "Frame Demo"))
; Directly call 'isVisible', or...
(printout t (call ?*frame* isVisible) crlf)
; ... equivalently, query the Bean property
(printout t (get ?*frame visible) crlf)