hengheng123456789

  BlogJava :: 首页 :: 联系 :: 聚合  :: 管理
  297 Posts :: 68 Stories :: 144 Comments :: 0 Trackbacks
 

JESS Beginning

 

1.        What is JESS

Jess is a rule engine and scripting environment written entirely in Sun's Java language by Ernest Friedman-Hill at Sandia National Laboratories in Livermore, CA. Using Jess, you can build Java software that has the capacity to "reason" using knowledge you supply in the form of declarative rules. Jess is small, light, and one of the fastest rule engines available. Its powerful scripting language gives you access to all of Java's APIs. Jess includes a full-featured development environment based on the award-winning Eclipse platform.

It's free for academic purpose and can be downloaded from http://herzberg.ca.sandia.gov/jess/. You are recommended to read the user's manual in the above webpage also.

 

 

2.        Getting ready

Jess has an interactive command-line interface. The distribution includes two scripts that you can run to get a Jess command prompt: one for Windows, and one for UNIX. They're both in the bin/ directory. Run the one that's appropriate for your system, and you should see something like this

C:\Jess70p1> bin\jess.bat

 

Jess, the Rule Engine for the Java Platform

Copyright (C) 2006 Sandia Corporation

Jess Version Jess70p1 9/5/2006

 

Jess>

 

That's the Jess prompt. Try evaluating a prefix math expression like "(+ 2 2)". Don't forget those parentheses!

Jess> (+ 2 2)

4

 

 

3.        Jess Language Basics

Most of the time, you'll write Jess rules in the Jess rule language. If you've never used Lisp, the Jess rule language may look a bit odd at first, but it doesn't take long to learn. The payoff is that it's very expressive, and can express complex logical relationships with very little code.

In this chapter, we'll look at the basic syntax of the Jess language. In subsequent chapters, we'll learn how to define high-level concepts like facts and rules, but here, we'll just be looking at the nuts and bolts.

In this language guide, I'll use an extremely informal notation to describe syntax. Basically strings in <angle-brackets> are some kind of data that must be supplied; things in [square brackets] are optional, things ending with + can appear one or more times, and things ending with * can appear zero or more times. In general, input to Jess is free-format. Newlines are generally not significant and are treated as whitespace; exceptions will be noted.

 

 

Though you can write Java programs and use the rete engine of Jess, it is sufficient that you interact with Jess shell alone. You don't need to know Java to program in Jess. You basically need to know only the Jess scripting language. You can define facts and rules from the command prompt. Jess stores them in its internal knowledge base, but never gives you access after you clear the memory. So, type your script in a separate file and call that file from Jess prompt. You can use whatever editor you want to edit your file. If the file name is cat.clp, you can load it by

Jess> (batch cat.clp)

 

All the facts that you give gets an id and goes into the knowledge base. The rules go to the rule base. When you run a Jess program, it runs as long as it doesn't find any rule to fire or gets a termination signal which is (halt) equivalent to exit()/System.exit() in other languages. In a sense, execution of a Jess program is basically firing of rules that keeps modifying the KB until the goal is reached. After you load your program using batch command, you should use the command reset to clear the working memory and load the facts into it. To run your program, use the command run. You can use clear to clear the working memory, but you should consult (batch) your file again after using this command.

 

Jess> (batch cat.clp) ;; loads the facts and rules into knowlege base

Jess> (reset) ;; clear the working memory loads them into working memory

Jess> (run) ;; runs the script

Jess> (clear) ;; clears Jess (after this reset or run will not work)

3.1.       Atoms

The atoms is the basic unit in Jess representation. It can contain letters or numbers or characters like $,*,=,+,/,<,>,_,?,#. The most important thing to note is that everything in Jess is CASE SENSITIVE. This might be the major source of errors when you start programming in Jess. The boolean values in Jess are TRUE and FALSE (again, they are case sensitive). The Jess equivalent of C's NULL or Java's null is nil.

 

3.2.       Numbers

Jess allows floating point and integer numbers. They are like any other language that you would have programmed with.

 

3.3.       Strings

The strings should be enclosed between double quotes. You can use the escape character if you want Jess to ignore double quotes. str-length can be used to find the length of a string.  str-cmp will let you compare two strings. It returns 0 if the two strings are identical, else a negative/positive value. You can find these functions like str-length implement in the source code StringFunctions.java.

Jess> (str-length "howdy")
5
Jess> (str-length "how\"dy")
6
Jess> (str-compare "Aggie" "Aggie")
0

 

3.4.       Lists

List is a collection of one or more atoms, strings, numbers or lists between parentheses.  As you can see, list is the base for the whole syntax of Jess.

Jess> (+ 1 2)
3

 

4.        Variables

4.1.       Binding

The variables should be denoted with a preceding "?". You can use bind to associate something to a variable.
Jess> (bind ?x "Howdy")

You don't have to declare variables before using them.

Jess> (bind ?x 1)
1
Jess> (= ?x 1)
TRUE
Jess> (> ?x 2)
FALSE

4.2.       Multi-field Variables


Multi-field variables are special variables with $ sign preceding them and they refer to a special kind of list. The following functions can be used with these variables.

Jess> (bind $?courses (create$ 625 629 613 606))         ;; creates a multi-field list and assign to multi-field variable
(625 629 613 606)                                                                 
Jess> $?courses                       
(625 629 613 606)
Jess> (first$ $?courses)                                                    ;; returns the first element
(625)  
Jess> (rest$ $?courses)                                                    ;; returns rest of the elements
(629 613 606)
Jess> (complement$ $?courses (create$ 601 602 603 604 605 629 613))   ;; returns all elements of second multi-field not in first one
(601 602 603 604 605)
Jess> (delete$ (create$ 100 101 102 103 104) 2 4)        ;; deletes between the range 2 and 4 (between 2nd and 4th element in list)
(100 104)

4.3.       Global Variables

All local variables that you might create will be destroyed when you execute the command reset. You can also have global variables. The global variables will persist even after using (reset) (i.e. even if you assign the variable to something else, it will still remain the initial one after reset). If you want the global variable to reflect the change even after reset, use (set-reset-globals nil)

Jess> (defglobal ?*a* = 100)
TRUE
Jess> ?*a*
100
Jess> (bind ?*a* 50)
50
Jess> (reset)
TRUE
Jess> ?*a*
100

 

5.        Type checking and Conversion

The following functions help in type checking (to see whether the variable is an integer etc and to cast a variable to another type. The examples below would be self explanatory.

5.1.       Type checking

-  (eq <expression> <expression>+)    ;; returns true if 1st exp is of the same type as others
-  (neq <expression> <expression>+)  ;; returns true if 1st exp is of the same type and value as others
-  (integerp <expression>)                   ;; returns true if the exp is an integer
-  (numberp <expression>)                  ;; returns true if the exp is a number

Jess> (bind ?x 1.4)
1.4
Jess> (eq ?x 1)
FALSE
Jess> (integerp ?x)
FALSE

5.2.       Type casting

It's similar to Java's typecasting. Generally, the use of casting in Jess won't be that common in your code.

- (float <numeric-expression>)       ;; casting to float
- (integer <numeric-expression>)   ;; casting to  integer

Jess> (bind ?x 1.4)
1.4
Jess> (= ?x 1)
FALSE
Jess> (= (integer ?x) 1)
TRUE

 

6.        Operators

6.1.       Arithmetic

The arithmetic operators are the same in Jess as any other standard programming language. But, you have to represent them in prefix form. If you want to say x++ somewhere in your program, you should say
(bind ?x (+ ?x 1))

6.2.       Comparison

For testing equality/inequality, you can use the operators in the same way. Remember, = is a comparison operator. For assignment, you should use bind.

Jess> (bind ?x 1)
1
Jess> (= ?x 1)
TRUE
Jess> (> ?x 2)
FALSE

6.3.       Logical

There are three logical operators available in Jess. They are and, or and not. The following example will help you understand their usages and functionalities.

Jess> (bind ?x 1)
1
Jess> (and (= ?x 1) (< ?x 0))
FALSE
Jess> (or (= ?x 1) (< ?x 0))
TRUE
Jess> (not (= ?x 5))
TRUE

As any other programming language, Jess allows the use of if-then-else statements and while loops. 

- (if <expression> then <action>+ else <action>+)
- (while <expression> do <action>+)

 

 

7.        Facts

The facts are stored in the knowledge base. You can do following things with facts.

7.1.       Add Facts

You can use assert to add a fact to the KB. Assert is especially helpful when you want to add a fact to a KB when a rule is fired or during the execution of the program. The examples are from the example code cat.clp.

(assert (Room (x 5) (has-milk TRUE)))  ;; asserts that 5th room has milk

However, to definite the initial condition of the world, it would be easy to use deffacts to assert all facts in one shot.

(deffacts init-room
  (Room (x 1))
  (Room (x 2))
  (Room (x 3))
  (Room (x 4))
  (Room (x 5) (has-milk TRUE))
)

7.2.       Remove Facts

You can remove facts using retract. However, the usage of retract isn't that simple as you have to first get the fact-id of the fact the concerned fact that you want to remove from KB and then remove it.
....
?room <- (Room (x 5) (has-milk TRUE))  ;; gets the fact id
=>
(retract ?room)                                          ;; removes the fact

7.3.       Using templates

You can roughly compare templates to a struct in C or Class in C++/Java. This is a very handy tool in representing different worlds. The fields inside a template are called slots. You should use deftemplate to define a template. The following example would be self explanatory on it's usage.

(deftemplate Cat
  (slot x (type integer) (default 1))
  (slot status (default hungry) )
  (slot smells-milk (default FALSE))
)

You can use modify to change the values of a slot. Look at the following example.
...
?cat <- (Cat(x 5))
=>
(modify ?cat (smells-milk TRUE))

8.        Rules

Rules are responsible for taking actions based on the facts in the KB. The rules have a left-hand-side part and a right-hand-side part with the "implies" operator in between. In a sense, all rules can be compared to a if-else statement in procedural languages. In procedural languages, the statements are executed line by line and only once. But, Jess fires all the rules continuously as long as it's left-hand-side is satisfied. You should use defrule to define a rule. The following example defines the rule for moving the cat from current room to the next room.

(defrule move
  ?cat <- (Cat(x ?x) (smells-milk FALSE))
=>
  (modify ?cat (x (+ ?x 1) ))
  (printout t "Cat moves to ["?x"]." crlf)
)


The above rule would be fired whenever the cat doesn't smell milk. Assume, we defined the initial states using deffunction and I want to ask the user whether he is ok with the default values of the location of milk. Now, we might have a conflict of which of these two rules should be executed? In procedural languages, it is easy as the code is executed line by line in a timely fashion. Here, if we have two or more rules with all preconditions satisfied, you should assign some priority (refered as salience) to each rule. The salience should be assigned while you define the rule and it is just a simple integer value. So, in a conflicting scenario, a rule with higher salience would be always executed. We can add the salience value as follows.

(defrule move
(declare (salience 50))
...
=>
....
)

8.1.       Functions

In the above discussions, we went through several inbuilt functions in Jess. In addition, you can also define your own function so that you don't have to dump everything inside rules. You should use deffunction to define a function. You can call the following function by calling  (change-default)

(deffunction change-default ()
   ......
  (assert (Room (x ?x) (has-milk TRUE)))
)

 

 

9.        Making Your Own Rules

In this chapter we're going to make a lot of use of a "person" template that looks like this:

Jess> (deftemplate person (slot firstName) (slot lastName) (slot age))

Rules are defined in Jess using the defrule construct. A very simple rule looks like this:

Jess> (defrule welcome-toddlers

    "Give a special greeting to young children"

    (person {age < 3})

    =>

    (printout t "Hello, little one!" crlf))

This rule has two parts, separated by the "=>" symbol (which you can read as "then".) The first part consists of the LHS pattern (person {age < 3}). The second part consists of the RHS action, the call to println. If you're new to Jess, it can be hard to tell the difference due to the LISP-like syntax, but the LHS of a rule consists of patterns which are used to match facts in the working memory, while the RHS contains function calls.

The LHS of a rule (the "if" part) consists of patterns that match facts, NOT function calls. The actions of a rule (the "then" clause) are made up of function calls. The following rule does NOT work:

Jess> (defrule wrong-rule

 (eq 1 1)

 =>

 (printout t "Just as I thought, 1 == 1!" crlf))

This rule will NOT fire just because the function call (eq 1 1) would evaluate to true. Instead, Jess will try to find a fact in the working memory that looks like (eq 1 1). Unless you have previously asserted such a fact, this rule will NOT be activated and will not fire. If you want to fire a rule based on the evaluation of a function that is not related to a pattern, you can use the test CE.

Our example rule, then, will be activated when an appropriate (person) fact appears in the working memory. When the rule executes, or fires, a message is printed. Let's turn this rule into a complete program. The function watch all tells Jess to print some useful diagnostics as we enter our program.

Jess> (deftemplate person (slot firstName) (slot lastName) (slot age))

TRUE

Jess> (watch all)

TRUE

Jess>  (reset)

==> f-0 (MAIN::initial-fact)

TRUE

Jess> (defrule welcome-toddlers

        "Give a special greeting to young children"

        (person {age < 3})

        =>

        (printout t "Hello, little one!" crlf))

welcome-toddlers: +1+1+1+t

TRUE

Jess>  (assert (person (age 2)))

==> f-1 (MAIN::person (firstName nil) (lastName nil) (age 2))

==> Activation: MAIN::welcome-toddlers : f-1

<Fact-1>

Some of these diagnostics are interesting. We see first of all how issuing the reset command asserts the fact (initial-fact). You should always issue a reset command when working with rules. When the rule itself is entered, we see the line "+1+1+t". This tells you something about how the rule is interpreted by Jess internally (see The Rete Algorithm for more information.) When the fact (person (age 2)) is asserted, we see the diagnostic "Activation: MAIN::welcome-toddlers : f-1". This means that Jess has noticed that the rule welcome-toddlers has all of its LHS conditions met by the given list of facts ("f-1").

After all this, our rule didn't fire; why not? Jess rules only fire while the rule engine is running (although they can be activated while the engine is not running.) To start the engine running, we issue the run command.

Jess> (run)

FIRE 1 MAIN::welcome-toddlers f-1

Hello, little one!

<== Focus MAIN

1

As soon as we enter the run command, the activated rule fires. Since we have watch all, Jess prints the diagnostic FIRE 1 welcome-toddlers f-1 to notify us of this. We then see the output of the rule's RHS actions. The final number "1" is the number of rules that fired (it is the return value of the run command.) The run function returns when there are no more activated rules to fire.

What would happen if we entered (run) again? Nothing. A rule will be activated only once for a given set of facts; once it has fired, that rule will not fire again for the same list of facts. We won't print the message again until another toddler shows up.

Rules are uniquely identified by their name. If a rule named my-rule exists, and you define another rule named my-rule, the first version is deleted and will not fire again, even if it was activated at the time the new version was defined.

 

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

10.1. The jess.Rete class

The jess.Rete class is the rule engine itself. Each jess.Rete object has its own working memory, agenda, rules, etc. To embed Jess in a Java application, you'll simply need to create 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.

10.1.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(), assertFact(Fact), retract(Fact), retract(int), and halt(). You can call these from Java just as you would from Jess.

10.1.2. Executing other Jess commands

You can use the Rete class's jess.Rete.eval(String) method to easily execute, from Java, any Jess function call or construct definition that can be represented as a parseable String. For example,

 

import jess.*;

public class ExSquare {

 public static void main(String[] unused) {

    try {

        Rete r = new Rete();

        r.eval("(deffunction square (?n) (return (* ?n ?n)))");

        Value v = r.eval("(square 3)");

 

        // Prints '9'

        System.out.println(v.intValue(r.getGlobalContext()));

    } catch (JessException ex) {

        System.err.println(ex);

    }

 }

}

C:\> java ExSquare

9

 

 

eval() returns the jess.Value object returned by the command. Commands executed via eval() 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 eval().

10.1.2.1. Optional commands

Note that when you create a Rete object from Java, it will already contain definitions for all of the functions that come with Jess. There are no longer any "optional" commands.

10.1.3. The script library

Some of Jess's commands are defined in Jess language code, in the file jess/scriptlib.clp. Each Rete object will load this script library when it is created and again if (clear) is called. In previous versions of Jess you had to do this yourself; this is no longer necessary.

10.1.4. Methods for adding, finding and listing constructs

The easiest (and still encouraged) way to define templates, 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. 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.Iterators of various data structures in the engine:

  • public Iterator listActivations()
  • public Iterator listDeffacts()
  • public Iterator listDefglobals()
  • public Iterator listDefrules()
  • public Iterator listDeftemplates()
  • public Iterator listFacts()
  • public Iterator listFunctions()

Note that the utility class jess.FilteringIterator is very convenient for use together with these methods. Together with a jess.Filter implementation, this class lets you create an Iterator that silently discards objects that don't match some criteria. So, for example, you could use the built-in filter jess.Filter.ByModule to iterate over only the facts in a given module:

 

import jess.*;

import java.util.*;

 

public class ExFilter {

    public static void main(String[] argv) throws JessException {

        // Run a Jess program...

        Rete engine = new Rete();

        engine.batch("somecode.clp");

       

        // ... now retrieve only the facts in a module named RESULTS

        Iterator it = new FilteringIterator(engine.listFacts(),

                                            new Filter.ByModule("RESULTS"));

    }

}

 

10.1.5. I/O Routers

Several Jess functions like printout, format, read, and readline take an I/O router name as an argument, while other functions like open return an I/O router name. An I/O router name is just a symbolic name for a Java java.io.Writer and/or a java.io.Reader. Each jess.Rete instance keeps separate tables of input routers and output routers, so that both a Reader and a Writer can be registered under the same name in each rule engine. When you call, for example, printout, Jess uses the first argument to look up the appropriate Writer in that table, and that's where the output will go.

The most commonly used router is t, which is used as Jess' standard input and output. Jess also has a built-in router named WSTDOUT for printing user messages internally -- for example, the Jess> prompt and the output of commands like facts and ppdefrule. The read and readline commands take input from the t router by default. Output from the watch function goes to the WSTDOUT router by default, but you can make it go to any other router using the jess.Rete.setWatchRouter(java.lang.String) method.

As startup, 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 (or any other router) to any Java java.io.Reader and java.io.Writer objects you choose. In fact, you can not only redirect the built-in routers, 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:

  • 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)
  • public void setWatchRouter(String s)

The words "input" and "output" are from the perspective of the Jess library itself; i.e., Jess reads from input routers and writes to output routers.

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.

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; getErrStream() does the same for WSTDERR.

10.1.6. TextAreaWriter, JTextAreaWriter and TextReader

Jess ships with three utility classes that can be very useful when building GUIs for Jess: the jess.awt.TextAreaWriter, jess.swing.JTextAreaWriter and jess.awt.TextReader classes. All three can serve as adapters between Jess and graphical input/output widgets. The TextAreaWriter class is, as the name implies, a Java java.io.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 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 ExTAW

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. JTextAreaWriter works the same way, but using a Swing javax.swing.JTextArea instead.

jess.awt.TextReader is similar, but it is a java.io.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.

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

Another important tip: the JessException class has a method getCause 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.getCause() will return the real exception that was thrown. Your JessException handlers should always check getCause(); if your handler simply displays a thrown exception, then it should display the return value of getCause(), too. getCause() replaces the now deprecated getNextException().

10.3. 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. Value supports 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 SYMBOL           =     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 jess.Fact object

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 list

final public static int DESCRIPTOR       = 1024; ; (internal use)

final public static int JAVA_OBJECT      = 2048; ; a Java object

final public static int INTARRAY         = 4096; ; (internal use)

final public static int MULTIVARIABLE    = 8192; ; a multifield

final public static int SLOT             = 16384; ; (internal use)

final public static int MULTISLOT        = 32768; ; (internal use)

final public static int LONG             = 65536; ; a Java long

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 javaObjectValue(Context c) throws JessException

public String stringValue(Context c) throws JessException

public Fact factValue(Context c) throws JessException

public Funcall 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 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.

10.3.1. The subclasses of jess.Value

jess.Value has a number of subclasses: jess.Variable, jess.FuncallValue, jess.FactIDValue, and jess.LongValue are the four of most interest to the reader. When you wish to create a value to represent a variable, a function call, a fact, or a Java long, 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.

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

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

10.3.1.3. The class jess.LongValue

Use this subclass of Value when you want to create a Value that represents a Java long. These are mostly used to pass to Java functions called via reflection. The one constructor looks like

public LongValue(long l) throws JessException

10.3.1.4. The class jess.FactIDValue

Use this subclass of Value when you want to create a Value that represents a fact-id. The one constructor looks like this:

public FactIDValue(Fact f) throws JessException

In 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 javaObjectValue(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.

10.3.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 (symbols, 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 Map, resolveValue() should always be called on both the key and object.

10.4. 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 a few of general importance.

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.

10.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, while specialized subclasses are used as function calls (jess.Funcall), facts (jess.Fact), and templates (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). Note that the jess.ValueVector.add method has several overloaded forms that convert primitives into jess.Value objects. The overload used here automatically converts its argument into a Jess symbol.

 

import jess.*;

public class ExABC {

 public static void main(String[] unused) throws JessException {

    ValueVector vv = new ValueVector();

    vv.add("a");

    vv.add("b");

    vv.add("c");

 

    // 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("a").add("b").add("c");

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

10.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 calls Jess's "set-reset-globals" function:

 

import jess.*;

public class ExResetGlobals {

 public static void main(String[] unused) throws JessException {

    Rete r = new Rete();

    Context c = r.getGlobalContext();

    Funcall f = new Funcall("set-reset-globals", r);

    f.arg(Funcall.FALSE);

    Value result = f.execute(c);

    System.out.println(result);

 }

}

C:\> java ExResetGlobals

FALSE

 

 

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 symbols nil, TRUE, FALSE, EOF, etc. You are encouraged to use these.

posted on 2007-06-28 18:11 哼哼 阅读(792) 评论(0)  编辑  收藏 所属分类: JAVA-Common

只有注册用户登录后才能发表评论。


网站导航: