JBoss.orgCommunity Documentation

Chapter 9. Examples

9.1. Getting the Examples
9.2. Hello World
9.3. State Example
9.3.1. Understanding the State Example
9.4. Fibonacci Example
9.5. Banking Tutorial
9.6. Pricing Rule Decision Table Example
9.6.1. Executing the example
9.6.2. The decision table
9.7. Pet Store Example
9.8. Honest Politician Example
9.9. Sudoku Example
9.9.1. Sudoku Overview
9.9.2. Running the Example
9.9.3. Java Source and Rules Overview
9.9.4. Sudoku Validator Rules (validatorSudoku.drl)
9.9.5. Sudoku Solving Rules (solverSudoku.drl)
9.9.6. Suggestions for Future Developments
9.10. Number Guess
9.11. Miss Manners and Benchmarking
9.11.1. Introduction
9.11.2. Indepth Discussion
9.11.3. Output Summary
9.12. Conway's Game Of Life

Make sure the Drools Eclipse plugin is installed, which needs the Graphical Editing Framework (GEF) dependency installed first. Then download and extract the drools-examples zip file, which includes an already created Eclipse project. Import that project into a new Eclipse workspace. The rules all have example classes that execute the rules. If you want to try the examples in another project (or another IDE) then you will need to set up the dependencies by hand, of course. Many, but not all of the examples are documented below, enjoy!

Some examples require Java 1.6 to run.

Name: Hello World
Main class: org.drools.examples.helloworld.HelloWorldExample
Type: Java application
Rules file: HelloWorld.drl
Objective: demonstrate basic rules in use

The "Hello World" example shows a simple example of rules usage, and both the MVEL and Java dialects.

This example demonstrates how to build Knowledge Bases and Sessions. Also, audit logging and debug outputs are shown, which is ommitted from other examples as it's all very similar. A KnowledgeBuilder is used to turn a DRL source file into Package objects which the Knowledge Base can consume. The add method takes a Resource interface and a Resource Type as parameters. The Resource can be used to retrieve a DRL source file from various locations; in this case the DRL file is being retrieved from the classpath using a ResourceFactory, but it could come from a disk file or a URL. Here, we only add a single DRL source file, but multiple DRL files can be added. Also, DRL files with different namespaces can be added, where the Knowledge Builder creates a package for each namespace. Multiple packages of different namespaces can be added to the same Knowledge Base. When all the DRL files have been added, we should check the builder for errors. While the Knowledge Base will validate the package, it will only have access to the error information as a String, so if you wish to debug the error information you should do it on the KnowledgeBuilder instance. Once you know the builder is error free, get the Package collection, instantiate a KnowledgeBase from the KnowledgeBaseFactory and add the package collection.


Drools has an event model that exposes much of what's happening internally. Two default debug listeners are supplied, DebugAgendaEventListener and DebugWorkingMemoryEventListener which print out debug event information to the System.err stream displayed in the Console window. Adding listeners to a Session is trivial, as shown below. The KnowledgeRuntimeLogger provides execution auditing, the result of which can be viewed in a graphical viewer. The logger is actually a specialised implementation built on the Agenda and Working Memory listeners. When the engine has finished executing, logger.close() must be called.

Most of the examples use the Audit logging features of Drools to record execution flow for later inspection.


The single class used in this example is very simple. It has two fields: the message, which is a String and the status which can be one of the two integers HELLO or GOODBYE.


A single Message object is created with the message text "Hello World" and the status HELLO and then inserted into the engine, at which point fireAllRules() is executed. Remember that all the network evaluation is done during the insert time, so that by the time the program execution reaches the fireAllRules() method call the engine already knows which rules are fully matches and able to fire.


To execute the example as a Java application:

  1. Open the class org.drools.examples.helloworld.HelloWorldExample in your Eclipse IDE

  2. Right-click the class and select "Run as..." and then "Java application"

If we put a breakpoint on the fireAllRules() method and select the ksession variable, we can see that the "Hello World" rule is already activated and on the Agenda, confirming that all the pattern matching work was already done during the insert.


The may application print outs go to to System.out while the debug listener print outs go to System.err.



The LHS (after when) section of the rule states that it will be activated for each Message object inserted into the Working Memory whose status is Message.HELLO. Besides that, two variable bindings are created: the variable message is bound to the message attribute and the variable m is bound to the matched Message object itself.

The RHS (after then) or consequence part of the rule is written using the MVEL expression language, as declared by the rule's attribute dialect. After printing the content of the bound variable message to System.out, the rule changes the values of the message and status attributes of the Message object bound to m. This is done MVEL's modify statement, which allows you to apply a block of assignments in one statement, with the engine being automatically notified of the changes at the end of the block.


We can set a breakpoint into the DRL, on the modify call, and inspect the Agenda view again during the execution of the rule's consequence. This time we start the execution via "Debug As" and "Drools application" and not by running a "Java application":

  1. Open the class org.drools.examples.HelloWorld in your Eclipse IDE.

  2. Right-click the class and select "Debug as..." and then "Drools application".

Now we can see that the other rule "Good Bye", which uses the Java dialect, is activated and placed on the Agenda.


The "Good Bye" rule, which specifies the "java" dialect, is similar to the "Hello World" rule except that it matches Message objects whose status is Message.GOODBYE.


Remember the Java code where we used the KnowledgeRuntimeLoggerFactory method newFileLogger to create a KnowledgeRuntimeLogger and called logger.close() at the end. This created an audit log file that can be shown in the Audit view. We use the Audit view in many of the examples to demostrate the example execution flow. In the view screen shot below we can see that the object is inserted, which creates an activation for the "Hello World" rule; the activation is then executed which updates the Message object causing the "Good Bye" rule to activate; finally the "Good Bye" rule also executes. Selecting an event in the Audit view highlights the origin event in green; therefore the "Activation created" event is highlighted in green as the origin of the "Activation executed" event.


This example is implemented in three different versions to demonstrate different ways of implementing the same basic behavior: forward chaining, i.e., the ability the engine has to evaluate, activate and fire rules in sequence, based on changes on the facts in the Working Memory.

Name: State Example
Main class: org.drools.examples.state.StateExampleUsingSalience
Type: Java application
Rules file: StateExampleUsingSalience.drl
Objective: Demonstrates basic rule use
  and Conflict Resolution for rule firing priority.

Each State class has fields for its name and its current state (see the class org.drools.examples.state.State). The two possible states for each objects are:


Ignoring the PropertyChangeSupport, which will be explained later, we see the creation of four State objects named A, B, C and D. Initially their states are set to NOTRUN, which is default for the used constructor. Each instance is asserted in turn into the Session and then fireAllRules() is called.


To execute the application:

  1. Open the class org.drools.examples.state.StateExampleUsingSalience in your Eclipse IDE.

  2. Right-click the class and select "Run as..." and then "Java application"

You will see the following output in the Eclipse console window:


There are four rules in total. First, the Bootstrap rule fires, setting A to state FINISHED, which then causes B to change its state to FINISHED. C and D are both dependent on B, causing a conflict which is resolved by the salience values. Let's look at the way this was executed.

The best way to understand what is happening is to use the Audit Logging feature to graphically see the results of each operation. To view the Audit log generated by a run of this example:

  1. If the Audit View is not visible, click on "Window" and then select "Show View", then "Other..." and "Drools" and finally "Audit View".

  2. In the "Audit View" click the "Open Log" button and select the file "<drools-examples-dir>/log/state.log".

After that, the "Audit view" will look like the following screenshot:


Reading the log in the "Audit View", top to bottom, we see every action and the corresponding changes in the Working Memory. This way we observe that the assertion of the State object A in the state NOTRUN activates the Bootstrap rule, while the assertions of the other State objects have no immediate effect.


The execution of rule Bootstrap changes the state of A to FINISHED, which, in turn, activates rule "A to B".


The execution of rule "A to B" changes the state of B to FINISHED, which activates both, rules "B to C" and "B to D", placing their Activations onto the Agenda. From this moment on, both rules may fire and, therefore, they are said to be "in conflict". The conflict resolution strategy allows the engine's Agenda to decide which rule to fire. As rule "B to C" has the higher salience value (10 versus the default salience value of 0), it fires first, modifying object C to state FINISHED. The Audit view shown above reflects the modification of the State object in the rule "A to B", which results in two activations being in conflict. The Agenda view can also be used to investigate the state of the Agenda, with debug points being placed in the rules themselves and the Agenda view opened. The screen shot below shows the breakpoint in the rule "A to B" and the state of the Agenda with the two conflicting rules.



Rule "B to D" fires last, modifying object D to state FINISHED.


There are no more rules to execute and so the engine stops.

Another notable concept in this example is the use of dynamic facts, based on PropertyChangeListener objects. As described in the documentation, in order for the engine to see and react to changes of fact properties, the application must tell the engine that changes occurred. This can be done explicitly in the rules by using the modify statement, or implicitly by letting the engine know that the facts implement PropertyChangeSupport as defined by the JavaBeans specification. This example demonstrates how to use PropertyChangeSupport to avoid the need for explicit modify statements in the rules. To make use of this feature, ensure that your facts implement PropertyChangeSupport, the same way the class org.drools.example.State does, and use the following code to insert the facts into the Working Memory:


When using PropertyChangeListener objects, each setter must implement a little extra code for the notification. Here is the setter for state in the class org.drools.examples:

:

There are two other classes in this example: StateExampleUsingAgendGroup and StateExampleWithDynamicRules. Both execute from A to B to C to D, as just shown. The StateExampleUsingAgendGroup uses agenda-groups to control the rule conflict and which one fires first. StateExampleWithDynamicRules shows how an additional rule can be added to an already running Working Memory with all the existing data applying to it at runtime.

Agenda groups are a way to partition the Agenda into groups and to control which groups can execute. By default, all rules are in the agenda group "MAIN". The "agenda-group" attribute lets you specify a different agenda group for the rule. Initially, a Working Memory has its focus on the Agenda group "MAIN". A group's rules will only fire when the group receives the focus. This can be achieved either ny using the method by setFocus() or the rule attribute auto-focus. "auto-focus" means that the rule automatically sets the focus to its agenda group when the rule is matched and activated. It is this "auto-focus" that enables rule "B to C" to fire before "B to D".


The rule "B to C" calls setFocus() on the agenda group "B to D", allowing its active rules to fire, which allows the rule "B to D" to fire.


The example StateExampleWithDynamicRules adds another rule to the Rule Base after fireAllRules(). The added rule is just another state transition.


This produces the following expected output:


Name: Fibonacci 
Main class: org.drools.examples.fibonacci.FibonacciExample
Type: Java application
Rules file: Fibonacci.drl
Objective: Demonstrates Recursion,
  the CE not and cross product matching

The Fibonacci Numbers (see http://en.wikipedia.org/wiki/Fibonacci_number) discovered by Leonardo of Pisa (see http://en.wikipedia.org/wiki/Fibonacci) is a sequence that starts with 0 and 1. The next Fibonacci number is obtained by adding the two preceding Fibonacci numbers. The Fibonacci sequence begins with 0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946,... The Fibonacci Example demonstrates recursion and conflict resolution with salience values.

The single fact class Fibonacci is used in this example. It has two fields, sequence and value. The sequence field is used to indicate the position of the object in the Fibonacci number sequence. The value field shows the value of that Fibonacci object for that sequence position, using -1 to indicate a value that still needs to be computed.


Execute the example:

  1. Open the class org.drools.examples.fibonacci.FibonacciExample in your Eclipse IDE.

  2. Right-click the class and select "Run as..." and then "Java application"

Eclipse shows the following output in its console window (with "...snip..." indicating lines that were removed to save space):


To kick this off from Java we only insert a single Fibonacci object, with a sequence field of 50. A recursive rule is then used to insert the other 49 Fibonacci objects. This example doesn't use PropertyChangeSupport. It uses the MVEL dialect, which means we can use the modify keyword, which allows a block setter action which also notifies the engine of changes.


The rule Recurse is very simple. It matches each asserted Fibonacci object with a value of -1, creating and asserting a new Fibonacci object with a sequence of one less than the currently matched object. Each time a Fibonacci object is added while the one with a sequence field equal to 1 does not exist, the rule re-matches and fires again. The not conditional element is used to stop the rule's matching once we have all 50 Fibonacci objects in memory. The rule also has a salience value, because we need to have all 50 Fibonacci objects asserted before we execute the Bootstrap rule.


The Audit view shows the original assertion of the Fibonacci object with a sequence field of 50, done from Java code. From there on, the Audit view shows the continual recursion of the rule, where each asserted Fibonacci object causes the Recurse rule to become activated and to fire again.


When a Fibonacci object with a sequence field of 2 is asserted the "Bootstrap" rule is matched and activated along with the "Recurse" rule. Note the multi-restriction on field sequence, testing for equality with 1 or 2.


At this point the Agenda looks as shown below. However, the "Bootstrap" rule does not fire because the "Recurse" rule has a higher salience.


When a Fibonacci object with a sequence of 1 is asserted the Bootstrap rule is matched again, causing two activations for this rule. Note that the "Recurse" rule does not match and activate because the not conditional element stops the rule's matching as soon as a Fibonacci object with a sequence of 1 exists.


Once we have two Fibonacci objects with values not equal to -1 the "Calculate" rule is able to match. It was the "Bootstrap" rule that set the objects with sequence 1 and 2 to values of 1. At this point we have 50 Fibonacci objects in the Working Memory. Now we need to select a suitable triple to calculate each of their values in turn. Using three Fibonacci patterns in a rule without field constraints to confine the possible cross products would result in 50x49x48 possible combinations, leading to about 125,000 possible rule firings, most of them incorrect. The "Calculate" rule uses field constraints to correctly constraint the thee Fibonacci patterns in the correct order; this technique is called cross product matching. The first pattern finds any Fibonacci with a value != -1 and binds both the pattern and the field. The second Fibonacci does this, too, but it adds an additional field constraint to ensure that its sequence is greater by one than the Fibonacci bound to f1. When this rule fires for the first time, we know that only sequences 1 and 2 have values of 1, and the two constraints ensure that f1 references sequence 1 and f2 references sequence 2. The final pattern finds the Fibonacci with a value equal to -1 and with a sequence one greater than f2. At this point, we have three Fibonacci objects correctly selected from the available cross products, and we can calculate the value for the third Fibonacci object that's bound to f3.


The modify statement updated the value of the Fibonacci object bound to f3. This means we now have another new Fibonacci object with a value not equal to -1, which allows the "Calculate" rule to rematch and calculate the next Fibonacci number. The Audit view below shows how the firing of the last "Bootstrap" modifies the Fibonacci object, enabling the "Calculate" rule to match, which then modifies another Fibonacci object allowing the "Calculate" rule to match again. This continues till the value is set for all Fibonacci objects.


Name: BankingTutorial
Main class: org.drools.tutorials.banking.BankingExamplesApp.java
Type: Java application
Rules file: org.drools.tutorials.banking.*.drl
Objective: Demonstrate pattern matching, basic sorting and calculation rules.

This tutorial demonstrates the process of developing a complete personal banking application to handle credits and debits on multiple accounts. It uses a set of design patterns that have been created for the process.

The class RuleRunner is a simple harness to execute one or more DRL files against a set of data. It compiles the Packages and creates the Knowledge Base for each execution, allowing us to easily execute each scenario and inspect the outputs. In reality this is not a good solution for a production system, where the Knowledge Base should be built just once and cached, but for the purposes of this tutorial it shall suffice.

Example 9.28. Banking Tutorial: RuleRunner

public class RuleRunner {


    public RuleRunner() {
    }
    public void runRules(String[] rules,
                         Object[] facts) throws Exception {
        KnowledgeBase kbase = KnowledgeBaseFactory.newKnowledgeBase();
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        for ( int i = 0; i < rules.length; i++ ) {
            String ruleFile = rules[i];
            System.out.println( "Loading file: " + ruleFile );
            kbuilder.add( ResourceFactory.newClassPathResource( ruleFile,
                                                                RuleRunner.class ),
                          ResourceType.DRL );
        }
        Collection<KnowledgePackage> pkgs = kbuilder.getKnowledgePackages();
        kbase.addKnowledgePackages( pkgs );
        StatefulKnowledgeSession ksession = kbase.newStatefulKnowledgeSession();
        for ( int i = 0; i < facts.length; i++ ) {
            Object fact = facts[i];
            System.out.println( "Inserting fact: " + fact );
            ksession.insert( fact );
        }
        ksession.fireAllRules();
    }
}

The first of our sample Java classes loads and executes a single DRL file, Example.drl, but without inserting any data.


The first simple rule to execute has a single eval condition that will alway be true, so that this rule will match and fire, once, after the start.


The output for the rule is below, showing that the rule matches and executes the single print statement.


The next step is to assert some simple facts and print them out.


This doesn’t use any specific facts but instead asserts a set of java.lang.Integer objects. This is not considered "best practice" as a number is not a useful fact, but we use it here to demonstrate basic techniques before more complexity is added.

Now we will create a simple rule to print out these numbers.


Once again, this rule does nothing special. It identifies any facts that are Number objects and prints out the values. Notice the use of the abstract class Number: we inserted Integer objects but we now look for any kind of number. The pattern matching engine is able to match interfaces and superclasses of asserted objects.

The output shows the DRL being loaded, the facts inserted and then the matched and fired rules. We can see that each inserted number is matched and fired and thus printed.


There are certainly many better ways to sort numbers than using rules, but since we will need to apply some cashflows in date order when we start looking at banking rules we'll develop simple rule based sorting technique.


Again we insert our Integer objects, but this time the rule is slightly different:


The first line of the rule identifies a Number and extracts the value. The second line ensures that there does not exist a smaller number than the one found by the first pattern. We might expect to match only one number - the smallest in the set. However, the retraction of the number after it has been printed means that the smallest number has been removed, revealing the next smallest number, and so on.

The resulting output shows that the numbers are now sorted numerically.


We are ready to start moving towards our personal accounting rules. The first step is to create a Cashflow object.


Class Cashflow has two simple attributes, a date and an amount. (Note that using the type double for monetary units is generally not a good idea because floating point numbers cannot represent most numbers accurately.) There is also an overloaded constructor to set the values, and a method toString to print a cashflow. The Java code of Example4.java inserts five Cashflow objects, with varying dates and amounts.


The convenience class SimpleDate extends java.util.Date, providing a constructor taking a String as input and defining a date format. The code is listed below


Now, let’s look at Example4.drl to see how we print the sorted Cashflow objects:


Here, we identify a Cashflow and extract the date and the amount. In the second line of the rule we ensure that there is no Cashflow with an earlier date than the one found. In the consequence, we print the Cashflow that satisfies the rule and then retract it, making way for the next earliest Cashflow. So, the output we generate is:


Next, we extend our Cashflow, resulting in a TypedCashflow which can be a credit or a debit operation. (Normally, we would just add this to the Cashflow type, but we use extension to keep the previous version of the class intact.)


There are lots of ways to improve this code, but for the sake of the example this will do.

Now let's create Example5, a class for running our code.


Here, we simply create a set of Cashflow objects which are either credit or debit operations. We supply them and Example5.drl to the RuleEngine.

Now, let’s look at a rule printing the sorted Cashflow objects.


Here, we identify a Cashflow fact with a type of CREDIT and extract the date and the amount. In the second line of the rule we ensure that there is no Cashflow of the same type with an earlier date than the one found. In the consequence, we print the cashflow satisfying the patterns and then retract it, making way for the next earliest cashflow of type CREDIT.

So, the output we generate is


Continuing our banking exercise, we are now going to process both credits and debits on two bank accounts, calculating the account balance. In order to do this, we create two separate Account objects and inject them into the Cashflows objects before passing them to the Rule Engine. The reason for this is to provide easy access to the correct account without having to resort to helper classes. Let’s take a look at the Account class first. This is a simple Java object with an account number and balance:


Now let’s extend our TypedCashflow, resulting in AllocatedCashflow, to include an Account reference.


The Java code of Example5.java creates two Account objects and passes one of them into each cashflow, in the constructor call.

Example 9.49. Banking Tutorial: Example5.java

public class Example6 {

    public static void main(String[] args) throws Exception {      
        Account acc1 = new Account(1);
        Account acc2 = new Account(2);
           
        Object[] cashflows = {
            new AllocatedCashflow(acc1,new SimpleDate("01/01/2007"),
                                  TypedCashflow.CREDIT, 300.00),
            new AllocatedCashflow(acc1,new SimpleDate("05/02/2007"),
                                  TypedCashflow.CREDIT, 100.00),
            new AllocatedCashflow(acc2,new SimpleDate("11/03/2007"),
                                  TypedCashflow.CREDIT, 500.00),
            new AllocatedCashflow(acc1,new SimpleDate("07/02/2007"),
                                  TypedCashflow.DEBIT,  800.00),
            new AllocatedCashflow(acc2,new SimpleDate("02/03/2007"),
                                  TypedCashflow.DEBIT,  400.00),
            new AllocatedCashflow(acc1,new SimpleDate("01/04/2007"),    
                                  TypedCashflow.CREDIT, 200.00),
            new AllocatedCashflow(acc1,new SimpleDate("05/04/2007"),
                                  TypedCashflow.CREDIT, 300.00),
            new AllocatedCashflow(acc2,new SimpleDate("11/05/2007"),
                                  TypedCashflow.CREDIT, 700.00),
            new AllocatedCashflow(acc1,new SimpleDate("07/05/2007"),
                                  TypedCashflow.DEBIT,  900.00),
            new AllocatedCashflow(acc2,new SimpleDate("02/05/2007"),
                                  TypedCashflow.DEBIT,  100.00)           
        };
        
        new RuleRunner().runRules( new String[] { "Example6.drl" },
                                   cashflows );
    }
}

Now, let’s look at the rule in Example6.drl to see how we apply each cashflow in date order and calculate and print the balance.


Although we have separate rules for credits and debits, but we do not specify a type when checking for earlier cashflows. This is so that all cashflows are applied in date order, regardless of the cashflow type. In the conditions we identify the account to work with, and in the consequences we update it with the cashflow amount.

Example 9.51. Banking Tutorial: Output of Example6.java

Loading file: Example6.drl
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon Jan 01 00:00:00 GMT 2007,type=Credit,amount=300.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon Feb 05 00:00:00 GMT 2007,type=Credit,amount=100.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Sun Mar 11 00:00:00 GMT 2007,type=Credit,amount=500.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Wed Feb 07 00:00:00 GMT 2007,type=Debit,amount=800.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Fri Mar 02 00:00:00 GMT 2007,type=Debit,amount=400.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Sun Apr 01 00:00:00 BST 2007,type=Credit,amount=200.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Thu Apr 05 00:00:00 BST 2007,type=Credit,amount=300.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Fri May 11 00:00:00 BST 2007,type=Credit,amount=700.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=1,balance=0.0],date=Mon May 07 00:00:00 BST 2007,type=Debit,amount=900.0]
Inserting fact: AllocatedCashflow[account=Account[accountNo=2,balance=0.0],date=Wed May 02 00:00:00 BST 2007,type=Debit,amount=100.0]
Debit: Fri Mar 02 00:00:00 GMT 2007 :: 400.0
Account: 2 - new balance: -400.0
Credit: Sun Mar 11 00:00:00 GMT 2007 :: 500.0
Account: 2 - new balance: 100.0
Debit: Wed May 02 00:00:00 BST 2007 :: 100.0
Account: 2 - new balance: 0.0
Credit: Fri May 11 00:00:00 BST 2007 :: 700.0
Account: 2 - new balance: 700.0
Credit: Mon Jan 01 00:00:00 GMT 2007 :: 300.0
Account: 1 - new balance: 300.0
Credit: Mon Feb 05 00:00:00 GMT 2007 :: 100.0
Account: 1 - new balance: 400.0
Debit: Wed Feb 07 00:00:00 GMT 2007 :: 800.0
Account: 1 - new balance: -400.0
Credit: Sun Apr 01 00:00:00 BST 2007 :: 200.0
Account: 1 - new balance: -200.0
Credit: Thu Apr 05 00:00:00 BST 2007 :: 300.0
Account: 1 - new balance: 100.0
Debit: Mon May 07 00:00:00 BST 2007 :: 900.0
Account: 1 - new balance: -800.0

The Pricing Rule decision table demonstrates the use of a decision table in a spreadsheet, in Excel's XLS format, in calculating the retail cost of an insurance policy. The purpose of the provide set of rules is to calculate a base price and a discount for a car driver applying for a specific policy. The driver's age, history and the policy type all contribute to what the basic premium is, and an additional chunk of rules deals with refining this with a discount percentage.

Name: Example Policy Pricing
Main class: org.drools.examples.decisiontable.PricingRuleDTExample
Type: Java application
Rules file: ExamplePolicyPricing.xls
Objective: demonstrate spreadsheet-based decision tables.

Open the file PricingRuleDTExample.java and execute it as a Java application. It should produce the following output in the Console window:

Cheapest possible
BASE PRICE IS: 120
DISCOUNT IS: 20     

The code to execute the example follows the usual pattern. The rules are loaded, the facts inserted and a Stateless Session is created. What is different is how the rules are added.

DecisionTableConfiguration dtableconfiguration =

    KnowledgeBuilderFactory.newDecisionTableConfiguration();
        dtableconfiguration.setInputType( DecisionTableInputType.XLS );
        KnowledgeBuilder kbuilder = KnowledgeBuilderFactory.newKnowledgeBuilder();
        Resource xlsRes = ResourceFactory.newClassPathResource( "ExamplePolicyPricing.xls",
                                                                getClass() );
        kbuilder.add( xlsRes,
                      ResourceType.DTABLE,
                      dtableconfiguration );

Note the use of the DecisionTableConfiguration object. Its input type is set to DecisionTableInputType.XLS. If you use the BRMS, all this is of course taken care of for you.

There are two fact types used in this example, Driver and Policy. Both are used with their default values. The Driver is 30 years old, has had no prior claims and currently has a risk profile of LOW. The Policy being applied for is COMPREHENSIVE, and it has not yet been approved.

In this decision table, each row is a rule, and each column is a condition or an action.


Referring to the spreadsheet show above, we have the RuleSet declaration, which provides the package name. There are also other optional items you can have here, such as Variables for global variables, and Imports for importing classes. In this case, the namespace of the rules is the same as the fact classes we are using, so we can omit it.

Moving further down, we can see the RuleTable declaration. The name after this (Pricing bracket) is used as the prefix for all the generated rules. Below that, we have "CONDITION or ACTION", indicating the purpose of the column, i.e., whether it forms part of the condition or the consequence of the rule that will be generated.

You can see that there is a driver, his data spanned across three cells, which means that the template expressions below it apply to that fact. We observe the driver's age range (which uses $1 and $2 with comma-separated values), locationRiskProfile, and priorClaims in the respective columns. In the action columns, we are set the policy base price and log a message.


In the preceding spreadsheet section, there are broad category brackets, indicated by the comment in the leftmost column. As we know the details of our drivers and their policies, we can tell (with a bit of thought) that they should match row number 18, as they have no prior accidents, and are 30 years old. This gives us a base price of 120.


The above section contains the conditions for the discount we might grant our driver. The discount results from the Age bracket, the number of prior claims, and the policy type. In our case, the driver is 30, with no prior claims, and is applying for a COMPREHENSIVE policy, which means we can give a discount of 20%. Note that this is actually a separate table, but in the same worksheet, so that different templates apply.

It is important to note that decision tables generate rules. This means they aren't simply top-down logic, but more a means to capture data resulting in rules. This is a subtle difference that confuses some people. The evaluation of the rules is not necessarily in the given order, since all the normal mechanics of the rule engine still apply.

Name: Pet Store 
Main class: org.drools.examples.petstore.PetStoreExample
Type: Java application
Rules file: PetStore.drl
Objective: Demonstrate use of Agenda Groups, Global Variables and integration with a GUI,
including callbacks from within the rules

The Pet Store example shows how to integrate Rules with a GUI, in this case a Swing based desktop application. Within the rules file, it demonstrates how to use Agenda groups and auto-focus to control which of a set of rules is allowed to fire at any given time. It also illustrates the mixing of the Java and MVEL dialects within the rules, the use of accumulate functions and the way of calling Java functions from within the ruleset.

All of the Java code is contained in one file, PetStore.java, defining the following principal classes (in addition to several classes to handle Swing Events):

Much of the Java code is either plain JavaBeans or Swing-based. Only a few Swing-related points will be discussed in this section, but a good tutorial about Swing components can be found at Sun's Swing website, in http://java.sun.com/docs/books/tutorial/uiswing/ .

The pieces of Java code in Petstore.java that relate to rules and facts are shown below.


The code shown above loads the rules from a DRL file on the classpath. Unlike other examples where the facts are asserted and fired straight away, this example defers this step to later. The way it does this is via the second last line where a PetStoreUI object is created using a constructor accepting the Vector object stock collecting our products, and an instance of the CheckoutCallback class containing the Rule Base that we have just loaded.

The Java code that fires the rules is within the CheckoutCallBack.checkout() method. This is triggered (eventually) when the Checkout button is pressed by the user.


Two items get passed into this method. One is the handle to the JFrame Swing component surrounding the output text frame, at the bottom of the GUI. The second is a list of order items; this comes from the TableModel storing the information from the "Table" area at the top right section of the GUI.

The for loop transforms the list of order items coming from the GUI into the Order JavaBean, also contained in the file PetStore.java. Note that it would be possible to refer to the Swing dataset directly within the rules, but it is better coding practice to do it this way, using simple Java objects. It means that we are not tied to Swing if we wanted to transform the sample into a Web application.

It is important to note that all state in this example is stored in the Swing components, and that the rules are effectively stateless. Each time the "Checkout" button is pressed, this code copies the contents of the Swing TableModel into the Session's Working Memory.

Within this code, there are nine calls to the Working Memory. The first of these creates a new Working Memory, as a Stateful Knowledge Session from the Knowledge Base. Remember that we passed in this Knowledge Base when we created the CheckoutCallBack class in the main() method. The next two calls pass in two objects that we will hold as global variables in the rules: the Swing text area and the Swing frame used for writing messages.

More inserts put information on products into the Working Memory, as well as the order list. The final call is the standard fireAllRules(). Next, we look at what this method causes to happen within the rules file.


The first part of file PetStore.drl contains the standard package and import statements to make various Java classes available to the rules. New to us are the two globals frame and textArea. They hold references to the Swing components JFrame and JTextArea components that were previously passed on by the Java code calling the setGlobal() method. Unlike variables in rules, which expire as soon as the rule has fired, global variables retain their value for the lifetime of the Session.

The next extract from the file PetStore.drl contains two functions that are referenced by the rules that we will look at shortly.

Example 9.55. Java Functions in the Rules: extract from PetStore.drl

function void doCheckout(JFrame frame, WorkingMemory workingMemory) {

    Object[] options = {"Yes",
                        "No"};
                            
    int n = JOptionPane.showOptionDialog(frame,
        "Would you like to checkout?",
        "",
        JOptionPane.YES_NO_OPTION,
        JOptionPane.QUESTION_MESSAGE,
        null,
        options,
        options[0]);
    if (== 0) {
        workingMemory.setFocus( "checkout" );
    }   
}
function boolean requireTank(JFrame frame, WorkingMemory workingMemory, Order order, Product fishTank, int total) {
    Object[] options = {"Yes",
                        "No"};
                            
    int n = JOptionPane.showOptionDialog(frame,
        "Would you like to buy a tank for your " + total + " fish?",
        "Purchase Suggestion",
        JOptionPane.YES_NO_OPTION,
        JOptionPane.QUESTION_MESSAGE,
        null,
        options,
        options[0]);
                                             
    System.out.print( "SUGGESTION: Would you like to buy a tank for your "
                      + total + " fish? - " );
    if (== 0) {
        Purchase purchase = new Purchase( order, fishTank );
        workingMemory.insert( purchase );
        order.addItem( purchase );
        System.out.println( "Yes" );
    } else {
        System.out.println( "No" );
    }      
    return true;
}

Having these functions in the rules file just makes the Pet Store example more compact. In real life you probably have the functions in a file of their own, within the same rules package, or as a static method on a standard Java class, and import them, using import function my.package.Foo.hello.

The purpose of these two functions is:

  • doCheckout() displays a dialog asking users whether they wish to checkout. If they do, focus is set to the checkOut agenda-group, allowing rules in that group to (potentially) fire.

  • requireTank() displays a dialog asking users whether they wish to buy a tank. If so, a new fish tank Product is added to the order list in Working Memory.

We'll see the rules that call these functions later on. The next set of examples are from the Pet Store rules themselves. The first extract is the one that happens to fire first, partly because it has the auto-focus attribute set to true.


This rule matches against all orders that do not yet have their grossTotal calculated . It loops for each purchase item in that order. Some parts of the "Explode Cart" rule should be familiar: the rule name, the salience (suggesting the order for the rules being fired) and the dialect set to "java". There are three new features:

  • agenda-group "init" defines the name of the agenda group. In this case, there is only one rule in the group. However, neither the Java code nor a rule consequence sets the focus to this group, and therefore it relies on the next attribute for its chance to fire.

  • auto-focus true ensures that this rule, while being the only rule in the agenda group, gets a chance to fire when fireAllRules() is called from the Java code.

  • kcontext....setFocus() sets the focus to the "show items" and "evaluate" agenda groups in turn, permitting their rules to fire. In practice, we loop through all items on the order, inserting them into memory, then firing the other rules after each insert.

The next two listings show the rules within the "show items" and evaluate agenda groups. We look at them in the order that they are called.


The "show items" agenda-group has only one rule, called "Show Items" (note the difference in case). For each purchase on the order currently in the Working Memory (or Session), it logs details to the text area at the bottom of the GUI. The textArea variable used to do this is one of the global variables we looked at earlier.

The evaluate Agenda group also gains focus from the "Explode Cart" rule listed previously. This Agenda group has two rules, "Free Fish Food Sample" and "Suggest Tank", shown below.


The rule "Free Fish Food Sample" will only fire if

  • we don't already have any fish food, and

  • we don't already have a free fish food sample, and

  • we do have a Gold Fish in our order.

If the rule does fire, it creates a new product (Fish Food Sample), and adds it to the order in Working Memory.

The rule "Suggest Tank" will only fire if

  • we don't already have a Fish Tank in our order, and

  • we do have more than 5 Gold Fish Products in our order.

If the rule does fire, it calls the requireTank() function that we looked at earlier (showing a Dialog to the user, and adding a Tank to the order / working memory if confirmed). When calling the requireTank() function the rule passes the global frame variable so that the function has a handle to the Swing GUI.

The next rule we look at is "do checkout".


The rule "do checkout" has no agenda group set and no auto-focus attribute. As such, is is deemed part of the default (MAIN) agenda group. This group gets focus by default when all the rules in agenda-groups that explicity had focus set to them have run their course.

There is no LHS to the rule, so the RHS will always call the doCheckout() function. When calling the doCheckout() function, the rule passes the global frame variable to give the function a handle to the Swing GUI. As we saw earlier, the doCheckout() function shows a confirmation dialog to the user. If confirmed, the function sets the focus to the checkout agenda-group, allowing the next lot of rules to fire.


There are three rules in the checkout agenda-group:

  • If we haven't already calculated the gross total, Gross Total accumulates the product prices into a total, puts this total into Working Memory, and displays it via the Swing JTextArea, using the textArea global variable yet again.

  • If our gross total is between 10 and 20, "Apply 5% Discount" calculates the discounted total and adds it to the Working Memory and displays it in the text area.

  • If our gross total is not less than 20, "Apply 10% Discount" calculates the discounted total and adds it to the Working Memory and displays it in the text area.

Now that we've run through what happens in the code, let's have a look at what happens when we actually run the code. The file PetStore.java contains a main() method, so that it can be run as a standard Java application, either from the command line or via the IDE. This assumes you have your classpath set correctly. (See the start of the examples section for more information.)

The first screen that we see is the Pet Store Demo. It has a list of available products (top left), an empty list of selected products (top right), checkout and reset buttons (middle) and an empty system messages area (bottom).


To get to this point, the following things have happened:

  1. The main() method has run and loaded the Rule Base but not yet fired the rules. So far, this is the only code in connection with rules that has been run.

  2. A new PetStoreUI object has been created and given a handle to the Rule Base, for later use.

  3. Various Swing components do their stuff, and the above screen is shown and waits for user input.

Clicking on various products from the list might give you a screen similar to the one below.


Note that no rules code has been fired here. This is only Swing code, listening for mouse click events, and adding some selected product to the TableModel object for display in the top right hand section. (As an aside, note that this is a classic use of the Model View Controller design pattern).

It is only when we press the "Checkout" button that we fire our business rules, in roughly the same order that we walked through the code earlier.

  1. Method CheckOutCallBack.checkout() is called (eventually) by the Swing class waiting for the click on the "Checkout" button. This inserts the data from the TableModel object (top right hand side of the GUI), and inserts it into the Session's Working Memory. It then fires the rules.

  2. The "Explode Cart" rule is the first to fire, given that it has auto-focus set to true. It loops through all the products in the cart, ensures that the products are in the Working Memory, and then gives the "Show Items" and Evaluation agenda groups a chance to fire. The rules in these groups add the contents of the cart to the text area (at the bottom of the window), decide whether or not to give us free fish food, and to ask us whether we want to buy a fish tank. This is shown in the figure below.


  1. The Do Checkout rule is the next to fire as it (a) No other agenda group currently has focus and (b) it is part of the default (MAIN) agenda group. It always calls the doCheckout() function which displays a 'Would you like to Checkout?' Dialog Box.

  2. The doCheckout() function sets the focus to the checkout agenda-group, giving the rules in that group the option to fire.

  3. The rules in the the checkout agenda-group display the contents of the cart and apply the appropriate discount.

  4. Swing then waits for user input to either checkout more products (and to cause the rules to fire again), or to close the GUI - see the figure below.


We could add more System.out calls to demonstrate this flow of events. The output, as it currently appears in the Console window, is given in the listing below.


Name: Honest Politician
Main class: org.drools.examples.honestpolitician.HonestPoliticianExample
Type: Java application
Rules file: HonestPoliticianExample.drl
Objective: Illustrate the concept of "truth maintenance" based on the logical insertion of facts

The Honest Politician example demonstrates truth maintenance with logical assertions. The basic premise is that an object can only exist while a statement is true. A rule's consequence can logically insert an object with the insertLogical() method. This means the object will only remain in the Working Memory as long as the rule that logically inserted it remains true. When the rule is no longer true the object is automatically retracted.

In this example there is the class Politician, with a name and a boolean value for being honest. Four politicians with honest state set to true are inserted.



The Console window output shows that, while there is at least one honest politician, democracy lives. However, as each politician is in turn corrupted by an evil corporation, so that all politicians become dishonest, democracy is dead.


As soon as there is at least one honest politician in the Working Memory a new Hope object is logically asserted. This object will only exist while there is at least one honest politician. As soon as all politicians are dishonest, the Hope object will be automatically retracted. This rule is given a salience of 10 to ensure that it fires before any other rule, as at this stage the "Hope is Dead" rule is actually true.


As soon as a Hope object exists the "Hope Lives" rule matches and fires. It has a salience of 10 so that it takes priority over "Corrupt the Honest".


Now that there is hope and we have, at the start, four honest politicians, we have four activations for this rule, all in conflict. They will fire in turn, corrupting each politician so that they are no longer honest. When all four politicians have been corrupted we have no politicians with the property honest == true. Thus, the rule "We have an honest Politician" is no longer true and the object it logical inserted (due to the last execution of new Hope()) is automatically retracted.


With the Hope object being automatically retracted, via the truth maintenance system, the conditional element not applied to Hope is no longer true so that the following rule will match and fire.


Let's take a look at the Audit trail for this application:


The moment we insert the first politician we have two activations. The rule "We have an honest Politician" is activated only once for the first inserted politician because it uses an exists conditional element, which matches once for any number. The rule "Hope is Dead" is also activated at this stage, because we have not yet inserted the Hope object. Rule "We have an honest Politician" fires first, as it has a higher salience than "Hope is Dead", which inserts the Hope object. (That action is highlighted green.) The insertion of the Hope object activates "Hope Lives" and de-activates "Hope is Dead"; it also activates "Corrupt the Honest" for each inserted honest politician. Rule "Hope Lives" executes, printing "Hurrah!!! Democracy Lives". Then, for each politician, rule "Corrupt the Honest" fires, printing "I'm an evil corporation and I have corrupted X", where X is the name of the politician, and modifies the politician's honest value to false. When the last honest politician is corrupted, Hope is automatically retracted, by the truth maintenance system, as shown by the blue highlighted area. The green highlighted area shows the origin of the currently selected blue highlighted area. Once the Hope fact is retracted, "Hope is dead" activates and fires printing "We are all Doomed!!! Democracy is Dead".

Name: Sudoku
Main class: org.drools.examples.sudoku.Main
Type: Java application
Rules file: sudokuSolver.drl, sudokuValidator.drl
Objective: Demonstrates the solving of logic problems, and complex pattern matching.

This example demonstrates how Drools can be used to find a solution in a large potential solution space based on a number of constraints. We use the popular puzzle of Sudoku. This example also shows how Drools can be integrated into a graphical interface and how callbacks can be used to interact with a running Drools rules engine in order to update the graphical interface based on changes in the Working Memory at runtime.

Download and install drools-examples as described above and then execute java org.drools.examples.sudoku.Main. This example requires Java 5.

A window will be displayed with a relatively simple partially filled grid.

Click on the "Solve" button and the Drools-based engine will fill out the remaining values. The Console window will display detailed information about the rules which are executing to solve the puzzle in a human readable form.

Rule #3 determined the value at (4,1) could not be 4 as this value already exists in the same column at (8,1)
Rule #3 determined the value at (5,5) could not be 2 as this value already exists in the same row at (5,6)
Rule #7 determined (3,5) is 2 as this is the only possible cell in the column that can have this value
Rule #1 cleared the other PossibleCellValues for (3,5) as a ResolvedCellValue of 2 exists for this cell.
Rule #1 cleared the other PossibleCellValues for (3,5) as a ResolvedCellValue of 2 exists for this cell.
... 
Rule #3 determined the value at (1,1) could not be 1 as this value already  exists in the same zone at (2,1)
Rule #6 determined (1,7) is 1 as this is the only possible cell in the row that can have this value
Rule #1 cleared the other PossibleCellValues for (1,7) as a ResolvedCellValue of 1 exists for this cell.
Rule #6 determined (1,1) is 8 as this is the only possible cell in the row that can have this value

Once all of the activated rules for the solving logic have executed, the engine executes a second rule base to check that the solution is complete and valid. In this case it is, and the "Solve" button is disabled and displays a text like "Solved (1052ms)".

The example comes with a number of grids which can be loaded and solved. Click on "File", then "Samples" and "Medium" to load a more challenging grid. Note that the solve button is enabled when the new grid is loaded.

Click on the "Solve" button again to solve this new grid.

Now, let us load a Sudoku grid that is deliberately invalid. Click on "File", "Samples" and "!DELIBERATELY BROKEN!". Note that this grid starts with some issues, for example the value 5 appears twice in the first row.

Nevertheless, click on the "Solve" button to apply the solving rules to this invalid grid. Note that the "Solve" button is relabelled to indicate that the resulting solution is invalid.

In addition, the validation rule set outputs all of the issues which are discovered to the console.

There are two cells on the same column with the same value at (6,0) and (4,0)
There are two cells on the same column with the same value at (4,0) and (6,0)
There are two cells on the same row with the same value at (2,4) and (2,2)
There are two cells on the same row with the same value at (2,2) and (2,4)
There are two cells on the same row with the same value at (6,3) and (6,8)
There are two cells on the same row with the same value at (6,8) and (6,3)
There are two cells on the same column with the same value at (7,4) and (0,4)
There are two cells on the same column with the same value at (0,4) and (7,4)
There are two cells on the same row with the same value at (0,8) and (0,0)
There are two cells on the same row with the same value at (0,0) and (0,8)
There are two cells on the same column with the same value at (1,2) and (3,2)
There are two cells on the same column with the same value at (3,2) and (1,2)
There are two cells in the same zone with the same value at (6,3) and (7,3)
There are two cells in the same zone with the same value at (7,3) and (6,3)
There are two cells on the same column with the same value at (7,3) and (6,3)
There are two cells on the same column with the same value at (6,3) and (7,3)

We will look at the solving rule set later in this section, but for the moment we should note that some theoretically solvable solutions can not be solved by the engine as it stands. Click on "File", "Samples" and then "Hard 3" to load a sparsely populated grid.

Now click on the "Solve" button and note that the current rules are unable to complete the grid, even though (if you are a Sudoku aficionado) you may be able to see a way forward with the solution.

At the present time, the solving functionality has been achieved by the use of ten rules. This rule set could be extended to enable the engine to tackle more complex logic for filling grids such as this.

The Java source code can be found in the /src/main/java/org/drools/examples/sudoku directory, with the two DRL files defining the rules located in the /src/main/rules/org/drools/examples/sudoku directory.

The package org.drools.examples.sudoku.swing contains a set of classes which implement a framework for Sudoku puzzles. Note that this package does not have any dependencies on the Drools libraries. SudokuGridModel defines an interface which can be implemented to store a Sudoku puzzle as a 9x9 grid of Integer values, some of which may be null, indicating that the value for the cell has not yet been resolved. SudokuGridView is a Swing component which can visualize any implementation of SudokuGridModel. SudokuGridEvent and SudokuGridListener are used to communicate state changes between the model and the view: events are fired when a cell's value is resolved or changed. If you are familiar with the model-view-controller patterns in other Swing components such as JTable then this pattern should be familiar. SudokuGridSamples provides a number of partially filled Sudoku puzzles for demonstration purposes.

Package org.drools.examples.sudoku.rules contains an implementation of SudokuGridModel which is based on Drools. Two Java objects are used, both of which extend AbstractCellValue and represent a value for a specific cell in the grid, including the row and column location of the cell, an index of the 3x3 zone the cell is contained in, and the value of the cell. PossibleCellValue indicates that we do not currently know for sure what the value in a cell is. There can be from 2 to 9 possible cell values for a given cell. ResolvedCellValue indicates that we have determined what the value for a cell must be. There can only be one resolved cell value for a given cell. DroolsSudokuGridModel implements SudokuGridModel and is responsible for converting an initial two dimensional array of partially specified cells into a set of CellValue Java object, creating a Working Memory based on solverSudoku.drl and inserting the CellValue objects into the Working Memory. When the solve() method is called it calls in turn fireAllRules() on this Working Memory to try to solve the puzzle. DroolsSudokuGridModel attaches a WorkingMemoryListener to the Working Memory, which allows it to be called back on insert and retract events as the puzzle is solved. When a new ResolvedCellValue is inserted into the Working Memory, this callback allows the implementation to fire a SudokuGridEvent to its SudokuGridListener clientele, which can then update themselves in realtime. Once all the rules fired by the solver Working Memory have executed, DroolsSudokuGridModel runs a second set of rules, based on validatorSudoku.drl which works with the same set of Java objects to determine whether the resulting grid is a valid and a full solution.

The class org.drools.examples.sudoku.Main implements a Java application combining the components desribed.

The packae org.drools.examples.sudoku contains two DRL files. solverSudoku.drl defines the rules which attempt to solve a Sudoku puzzle, and validator.drl defines the rules which determin whether the current state of the Working Memory represents a valid solution. Both use PossibleCellValue and ResolvedCellValue objects as their facts and both output information to the Console window as their rules fire. In a real-world situation we would insert logging information and use the WorkingMemoryListener to display this information to a user, rather than use the console in this fashion.

Now let us look at the more complex rule set used to solve Sudoku puzzles.

Rule #1 is basically a book-keeping rule. Several of the other rules insert ResolvedCellValues into the working memory at specific rows and columns after they have determined that a given cell must have a certain value. At this point, it is important to clear the Working Memory of any inserted PossibleCellValues at the same row and column with invalid values. This rule is therefore given a higher salience than the remaining rules to ensure that as soon as the LHS is true, activations for the rule move to the top of the Agenda and are fired. In turn, this prevents the spurious firing of other rules due to the combination of a ResolvedCellValue and one or more PossibleCellValues being present in the same cell. This rule also calls update() on the ResolvedCellValue, even though its value has not in fact been modified to ensure that Drools fires an event to any WorkingMemoryListeners attached to the Working Memory so that they can update themselves - in this case so that the GUI can display the new state of the grid.

Rule #2 identifies cells in the grid which have only one possible value. The first line of the when clause matches all of the PossibleCellValue objects in the Working Memory. The second line demonstrates a use of the not keyword. This rule will only fire if no other PossibleCellValue objects exist in the Working Memory at the same row and column but with a different value. When the rule fires, the single PossibleCellValue at the row and column is retracted from the Working Memory and is replaced by a new ResolvedCellValue at the same row and column with the same value.

Rule #3 removes PossibleCellValues with a given value from a row when they have the same value as a ResolvedCellValue. In other words, when a cell is filled with a resolved value, we need to remove the possibility of any other cell on the same row having this value. The first line of the when clause matches all ResolvedCellValue objects in the Working Memory. The second line matches PossibleCellValues which have both the same row and the same value as these ResolvedCellValue objects. If any are found, the rule activates and, when fired retracts the PossibleCellValue which can no longer be a solution for that cell.

Rules #4 and #5 act in the same way as Rule #3 but check for redundant PossibleCellValues in a given column and a given zone of the grid as a ResolvedCellValue respectively.

Rule #6 checks for the scenario where a possible cell value only appears once in a given row. The first line of the LHS matches against all PossibleCellValue facts in the Working Memory, storing the result in a number of local variables. The second line checks that no other PossibleCellValue objects with the same value exist on this row. The third to fifth lines check that there is not a ResolvedCellValue with the same value in the same zone, row or column so that this rule does not fire prematurely. It is interesting to note that we could remove lines 3 to 5 and give rules #3, #4 and #5 a higher salience to make sure they always fire before rules #6,#7 and #8. When the rule fires, we know that $possible must represent the value for the cell; so, as in Rule #2, we retract $possible and replace it with the equivalent, new ResolvedCellValue.

Rules #7 and #8 act in the same way as Rule #2 but check for single PossibleCellValues in a given column and a given zone of the grid, respectively.

Rule #9 represents the most complex currently implemented rule. This rule implements the logic that, if we know that a pair of given values can only occur in two cells on a specific row, (for example we have determined the values of 4 and 6 can only appear in the first row in cells [0,3] and [0,5]) and this pair of cells can not hold other values, then, although we do not know which of the pair contains a four and which contains a six, we do know that these two values must be in these two cells, and hence we can remove the possibility of them occuring anywhere else in the same row.

Rules #10 and #11 act in the same way as rule #9 but check for the existance of only two possible values in a given column and zone, respectively.

To solve harder grids, the rule set would need to be extended further with more complex rules that encapsulate more complex reasoning.

There are a number of ways in which this example could be developed. The reader is encouraged to consider these as excercises.

Name: Number Guess 
Main class: org.drools.examples.numberguess.NumberGuessExample
Type: Java application
Rules file: NumberGuess.drl
Objective: Demonstrate use of Rule Flow to organise Rules

The "Number Guess" example shows the use of Rule Flow, a way of controlling the order in which rules are fired. It uses widely understood workflow diagrams for defining the order in which groups of rules will be executed.


The creation of the package and the loading of the rules (using the add() method) is the same as the previous examples. There is an additional line to add the Rule Flow (NumberGuess.rf), which provides the option of specifying different rule flows for the same Knowledge Base. Otherwise, the Knowledge Base is created in the same manner as before.


Once we have a Knowledge Base, we can use it to obtain a Stateful Session. Into our session we insert our facts, i.e., standard Java objects. (For simplicity, in this sample, these classes are all contained within our NumberGuessExample.java file. Class GameRules provides the maximum range and the number of guesses allowed. Class RandomNumber automatically generates a number between 0 and 100 and makes it available to our rules, by insertion via the getValue() method. Class Game keeps track of the guesses we have made before, and their number.

Note that before we call the standard fireAllRules() method, we also start the process that we loaded earlier, via the startProcess() method. We'll learn where to obtain the parameter we pass ("Number Guess", i.e., the identifier of the rule flow) when we talk about the rule flow file and the graphical Rule Flow Editor below.

Before we finish the discussion of our Java code, we note that in some real-life application we would examine the final state of the objects. (Here, we could retrieve the number of guesses, to add it to a high score table.) For this example we are content to ensure that the Working Memory session is cleared by calling the dispose() method.


If you open the NumberGuess.rf file in the Drools IDE (provided you have the JBoss Rules extensions installed correctly in Eclipse) you should see the above diagram, similar to a standard flowchart. Its icons are similar (but not exactly the same) as in the JBoss jBPM workflow product. Should you wish to edit the diagram, a menu of available components should be available to the left of the diagram in the IDE, which is called the palette. This diagram is saved in XML, an (almost) human readable format, using XStream.

If it is not already open, ensure that the Properties View is visible in the IDE. It can be opened by clicking "Window", then "Show View" and "Other", where you can select the "Properties" view. If you do this before you select any item on the rule flow (or click on the blank space in the rule flow) you should be presented with the following set of properties.


Keep an eye on the Properties View as we progress through the example's rule flow, as it presents valuable information. In this case, it provides us with the identification of the Rule Flow Process that we used in our earlier code snippet, when we called session.startProcess().

In the "Number Guess" Rule Flow we encounter several node types, many of them identified by an icon.

  • The Start node (white arrow in a green circle) and the End node (red box) mark beginning and end of the rule flow.

  • A Rule Flow Group box (yellow, without an icon) represents a Rule Flow Groups defined in our rules (DRL) file that we will look at later. For example, when the flow reaches the Rule Flow Group "Too High", only those rules marked with an attribute of ruleflow-group "Too High" can potentially fire.

  • Action nodes (yellow, cog-shaped icon) perform standard Java method calls. Most action nodes in this example call System.out.println(), indicating the program's progress to the user.

  • Split and Join Nodes (blue ovals, no icon) such as "Guess Correct?" and "More guesses Join" mark places where the flow of control can split, according to various conditions, and rejoin, respectively

  • Arrows indicate the flow between the various nodes.

The various nodes in combination with the rules make the Number Guess game work. For example, the "Guess" Rule Flow Group allows only the rule "Get user Guess" to fire, because only that rule has a matching attribute of ruleflow-group "Guess".


The rest of this rule is fairly standard. The LHS section (after when) of the rule states that it will be activated for each RandomNumber object inserted into the Working Memory where guessCount is less than allowedGuesses from the GameRules object and where the user has not guessed the correct number.

The RHS section (or consequence, after then) prints a message to the user and then awaits user input from System.in. After obtaining this input (the readLine() method call blocks until the return key is pressed) it modifies the guess count and inserts the new guess, making both available to the Working Memory.

The rest of the rules file is fairly standard: the package declares the dialect as MVEL, and various Java classes are imported. In total, there are five rules in this file:

  1. Get User Guess, the Rule we examined above.

  2. A Rule to record the highest guess.

  3. A Rule to record the lowest guess.

  4. A Rule to inspect the guess and retract it from memory if incorrect.

  5. A Rule that notifies the user that all guesses have been used up.

One point of integration between the standard Rules and the RuleFlow is via the ruleflow-group attribute on the rules, as dicussed above. A second point of integration between the rules (.drl) file and the Rules Flow .rf files is that the Split Nodes (the blue ovals) can use values in the Working Memory (as updated by the rules) to decide which flow of action to take. To see how this works, click on the "Guess Correct Node"; then within the Properties View, open the Constraints Editor by clicking the button at the right that appears once you click on the "Constraints" property line. You should see something similar to the diagram below.


Click on the "Edit" button beside "To node Too High" and you'll see a dialog like the one below. The values in the "Textual Editor" window follow the standard rule format for the LHS and can refer to objects in Working Memory. The consequence (RHS) is that the flow of control follows this node (i.e., "To node Too High") if the LHS expression evaluates to true.


Since the file NumberGuess.java contains a main() method, it can be run as a standard Java application, either from the command line or via the IDE. A typical game might result in the interaction below. The numbers in bold are typed in by the user.


A summary of what is happening in this sample is:

  1. The main() method of NumberGuessExample.java loads a Rule Base, creates a Stateful Session and inserts Game, GameRules and RandomNumber (containing the target number) objects into it. The method also sets the process flow we are going to use, and fires all rules. Control passes to the Rule Flow.

  2. File NumberGuess.rf, the Rule Flow, begins at the "Start" node.

  3. Control passes (via the "More guesses" join node) to the Guess node.

  4. At the Guess node, the appropriate Rule Flow Group ("Get user Guess") is enabled. In this case the Rule "Guess" (in the NumberGuess.drl file) is triggered. This rule displays a message to the user, takes the response, and puts it into Working Memory. Flow passes to the next Rule Flow Node.

  5. At the next node, "Guess Correct", constraints inspect the current session and decide which path to take.

    If the guess in step 4 was too high or too low, flow proceeds along a path which has an action node with normal Java code printing a suitable message and a Rule Flow Group causing a highest guess or lowest guess rule to be triggered. Flow passes from these nodes to step 6.

    If the guess in step 4 was right, we proceed along the path towards the end of the Rule Flow. Before we get there, an action node with normal Java code prints a statement "you guessed correctly". There is a join node here (just before the Rule Flow end) so that our no-more-guesses path (step 7) can also terminate the Rule Flow.

  6. Control passes as per the Rule Flow via a join node, a guess incorrect Rule Flow Group (triggering a rule to retract a guess from Working Memory) onto the "More guesses" decision node.

  7. The "More guesses" decision node (on the right hand side of the rule flow) uses constraints, again looking at values that the rules have put into the working memory, to decide if we have more guesses and if so, goto step 3. If not, we proceed to the end of the rule flow, via a Rule Flow Group that triggers a rule stating "you have no more guesses".

  8. The loop over steps 3 to 7 continues until the number is guessed correctly, or we run out of guesses.

Name: Miss Manners
Main class: org.drools.benchmark.manners.MannersBenchmark
Type: Java application
Rules file: manners.drl
Objective: Advanced walkthrough on the Manners benchmark, covers Depth conflict resolution in depth.

Miss Manners is throwing a party and, being a good host, she wants to arrange good seating. Her initial design arranges everyone in male-female pairs, but then she worries about people have things to talk about. What is a good host to do? She decides to note the hobby of each guest so she can then arrange guests not only pairing them according to alternating sex but also ensuring that a guest has someone with a common hobby, at least on one side.


Before going deeper into the rules, let's first take a look at the asserted data and the resulting seating arrangement. The data is a simple set of five guests who should be arranged so that sexes alternate and neighbors have a common hobby.

The Data

The data is given in OPS5 syntax, with a parenthesized list of name and value pairs for each attribute. Each person has only one hobby.

The Results

Each line of the results list is printed per execution of the "Assign Seat" rule. They key bit to notice is that each line has a "pid" value one greater than the last. (The significance of this will be explained in the discussion of the rule "Assign Seating".) The "ls", "rs", "ln" and "rn" refer to the left and right seat and neighbor's name, respectively. The actual implementation uses longer attribute names (e.g., leftGuestName, but here we'll stick to the notation from the original implementation.

The Manners benchmark was written for OPS5 which has two conflict resolution strategies, LEX and MEA. LEX is a chain of several strategies including salience, recency and complexity. The recency part of the strategy drives the depth first (LIFO) firing order. The CLIPS manual documents the Recency strategy as follows:

 

Every fact and instance is marked internally with a "time tag" to indicate its relative recency with respect to every other fact and instance in the system. The pattern entities associated with each rule activation are sorted in descending order for determining placement. An activation with a more recent pattern entity is placed before activations with less recent pattern entities. To determine the placement order of two activations, compare the sorted time tags of the two activations one by one starting with the largest time tags. The comparison should continue until one activation’s time tag is greater than the other activation’s corresponding time tag. The activation with the greater time tag is placed before the other activation on the agenda. If one activation has more pattern entities than the other activation and the compared time tags are all identical, then the activation with more time tags is placed before the other activation on the agenda.

 
 --CLIPS Reference Manual

However Jess and CLIPS both use the Depth strategy, which is simpler and lighter, which Drools also adopted. The CLIPS manual documents the Depth strategy as:

 

Newly activated rules are placed above all rules of the same salience. For example, given that fact-a activates rule-1 and rule-2 and fact-b activates rule-3 and rule-4, then if fact-a is asserted before fact-b, rule-3 and rule-4 will be above rule-1 and rule-2 on the agenda. However, the position of rule-1 relative to rule-2 and rule-3 relative to rule-4 will be arbitrary.

 
 --CLIPS Reference Manual

The initial Drools implementation for the Depth strategy would not work for Manners without the use of salience on the "make_path" rule. The CLIPS support team had this to say:

 

The default conflict resolution strategy for CLIPS, Depth, is different than the default conflict resolution strategy used by OPS5. Therefore if you directly translate an OPS5 program to CLIPS, but use the default depth conflict resolution strategy, you're only likely to get the correct behavior by coincidence. The LEX and MEA conflict resolution strategies are provided in CLIPS to allow you to quickly convert and correctly run an OPS5 program in CLIPS.

 
 --Clips Support Forum

Investigation into the CLIPS code reveals there is undocumented functionality in the Depth strategy. There is an accumulated time tag used in this strategy; it's not an extensively fact by fact comparison as in the recency strategy, it simply adds the total of all the time tags for each activation and compares.

This rule determines each of the Seating arrangements. The rule creates cross product solutions for all asserted Seating arrangements against all the asserted guests except against itself or any already assigned chosen solutions.

rule findSeating
   when 
       context : Context( state == Context.ASSIGN_SEATS )
       $s      : Seating( pathDone == true )
       $g1     : Guest( name == $s.rightGuestName )
       $g2     : Guest( sex != $g1.sex, hobby == $g1.hobby )

       count   : Count()

       not ( Path( id == $s.id, guestName == $g2.name) )
       not ( Chosen( id == $s.id, guestName == $g2.name, hobby == $g1.hobby) )
   then
       int rightSeat = $s.getRightSeat();
       int seatId = $s.getId();
       int countValue = count.getValue();
       
       Seating seating =
         new Seating( countValue, seatId, false, rightSeat,
                      $s.getRightGuestName(), rightSeat + 1, $g2.getName() );
       insert( seating );
                            
       Path path = new Path( countValue, rightSeat + 1, $g2.getName()  );
       insert( path );
       
       Chosen chosen = new Chosen( seatId, $g2.getName(), $g1.getHobby() );
       insert( chosen  );

       System.err.println( "find seating : " + seating + " : " + path +
                           " : " + chosen);

       modify( count ) {setValue(  countValue + 1 )}
       modify( context ) {setState( Context.MAKE_PATH )}
end

However, as can be seen from the printed results shown earlier, it is essential that only the Seating with the highest pid cross product be chosen. How can this be possible if we have activations, of the same time tag, for nearly all existing Seating and Guest objects? For example, on the third iteration of findDeating the produced activations will be as shown below. Remember, this is from a very small data set, and with larger data sets there would be many more possible activated Seating solutions, with multiple solutions per pid:

The creation of all these redundant activations might seem pointless, but it must be remembered that Manners is not about good rule design; it's purposefully designed as a bad ruleset to fully stress-test the cross product matching process and the Agenda, which this clearly does. Notice that each activation has the same time tag of 35, as they were all activated by the change in the Context object to ASSIGN_SEATS. With OPS5 and LEX it would correctly fire the activation with the Seating asserted last. With Depth, the accumulated fact time tag ensures that the activation with the last asserted Seating fires.

Assign First seat
=>[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
=>[fid:14:14]:[Path id=1, seat=1, guest=n5]

==>[ActivationCreated(16): rule=findSeating
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

==>[ActivationCreated(16): rule=findSeating
[fid:13:13]:[Seating id=1 , pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]*

Assign Seating
=>[fid:15:17] :[Seating id=2 , pid=1 , done=false, ls=1, lg=n5, rs=2, rn=n4]
=>[fid:16:18]:[Path id=2, seat=2, guest=n4]
=>[fid:17:19]:[Chosen id=1, name=n4, hobbies=h1]

=>[ActivationCreated(21): rule=makePath 
[fid:15:17] : [Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4]
[fid:14:14] : [Path id=1, seat=1, guest=n5]*

==>[ActivationCreated(21): rule=pathDone
[Seating id=2, pid=1, done=false, ls=1, ln=n5, rs=2, rn=n4]*

Make Path
=>[fid:18:22:[Path id=2, seat=1, guest=n5]]

Path Done

Continue Process
=>[ActivationCreated(25): rule=findSeating
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4]
[fid:7:7]:[Guest name=n4, sex=f, hobbies=h3]
[fid:4:4] : [Guest name=n3, sex=m, hobbies=h3]*

=>[ActivationCreated(25): rule=findSeating
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4]
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1], [fid:12:20] : [Count value=3]

=>[ActivationCreated(25): rule=findSeating
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

Assign Seating
=>[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, lnn4, rs=3, rn=n3]]
=>[fid:20:27]:[Path id=3, seat=3, guest=n3]]
=>[fid:21:28]:[Chosen id=2, name=n3, hobbies=h3}]

=>[ActivationCreated(30): rule=makePath
[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]
[fid:18:22]:[Path id=2, seat=1, guest=n5]*

=>[ActivationCreated(30): rule=makePath 
[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]
[fid:16:18]:[Path id=2, seat=2, guest=n4]*

=>[ActivationCreated(30): rule=done 
[fid:19:26]:[Seating id=3, pid=2, done=false, ls=2, ln=n4, rs=3, rn=n3]*

Make Path
=>[fid:22:31]:[Path id=3, seat=1, guest=n5]

Make Path 
=>[fid:23:32] [Path id=3, seat=2, guest=n4]

Path Done

Continue Processing
=>[ActivationCreated(35): rule=findSeating
[fid:19:33]:[Seating id=3, pid=2, done=true, ls=2, ln=n4, rs=3, rn=n3]
[fid:4:4]:[Guest name=n3, sex=m, hobbies=h3]
[fid:3:3]:[Guest name=n2, sex=f, hobbies=h3], [fid:12:29]*

=>[ActivationCreated(35): rule=findSeating 
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] 
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1]
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1]

=>[ActivationCreated(35): rule=findSeating 
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5] 
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1], [fid:1:1] : [Guest name=n1, sex=m, hobbies=h1]

Assign Seating
=>[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]]
=>[fid:25:37]:[Path id=4, seat=4, guest=n2]]
=>[fid:26:38]:[Chosen id=3, name=n2, hobbies=h3]

==>[ActivationCreated(40): rule=makePath 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]
[fid:23:32]:[Path id=3, seat=2, guest=n4]*

==>[ActivationCreated(40): rule=makePath 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2] 
[fid:20:27]:[Path id=3, seat=3, guest=n3]*

=>[ActivationCreated(40): rule=makePath 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]
[fid:22:31]:[Path id=3, seat=1, guest=n5]*

=>[ActivationCreated(40): rule=done 
[fid:24:36]:[Seating id=4, pid=3, done=false, ls=3, ln=n3, rs=4, rn=n2]*

Make Path 
=>fid:27:41:[Path id=4, seat=2, guest=n4]

Make Path
=>fid:28:42]:[Path id=4, seat=1, guest=n5]]

Make Path
=>fid:29:43]:[Path id=4, seat=3, guest=n3]]

Path Done

Continue  Processing
=>[ActivationCreated(46): rule=findSeating 
[fid:15:23]:[Seating id=2, pid=1, done=true, ls=1, ln=n5, rs=2, rn=n4] 
[fid:5:5]:[Guest name=n4, sex=m, hobbies=h1], [fid:2:2]
[Guest name=n2, sex=f, hobbies=h1]

=>[ActivationCreated(46): rule=findSeating 
[fid:24:44]:[Seating id=4, pid=3, done=true, ls=3, ln=n3, rs=4, rn=n2]
[fid:2:2]:[Guest name=n2, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]*

=>[ActivationCreated(46): rule=findSeating 
[fid:13:13]:[Seating id=1, pid=0, done=true, ls=1, ln=n5, rs=1, rn=n5]
[fid:9:9]:[Guest name=n5, sex=f, hobbies=h1]
[fid:1:1]:[Guest name=n1, sex=m, hobbies=h1]

Assign Seating
=>[fid:30:47]:[Seating id=5, pid=4, done=false, ls=4, ln=n2, rs=5, rn=n1]
=>[fid:31:48]:[Path id=5, seat=5, guest=n1]
=>[fid:32:49]:[Chosen id=4, name=n1, hobbies=h1]

Name: Conway's Game Of Life
Main class: org.drools.examples.conway.ConwayAgendaGroupRun
            org.drools.examples.conway.ConwayRuleFlowGroupRun
Type: Java application
Rules file: conway-ruleflow.drl conway-agendagroup.drl
Objective: Demonstrates 'accumulate', 'collect' and 'from'

Conway's Game Of Life, described in http://en.wikipedia.org/wiki/Conway's_Game_of_Life and in http://www.math.com/students/wonders/life/life.html, is a famous cellular automaton conceived in the early 1970's by the mathematician John Conway. While the system is well known as "Conway's Game Of Life", it really isn't a game at all. Conway's system is more like a simulation of a form of life. Don't be intimidated. The system is terribly simple and terribly interesting. Math and Computer Science students alike have marvelled over Conway's system for more than 30 years now. The application presented here is a Swing-based implementation of Conway's Game of Life. The rules that govern the system are implemented as business rules using Drools. This document will explain the rules that drive the simulation and discuss the Drools parts of the implementation.

We'll first introduce the grid view, shown below, designed for the visualisation of the game, showing the "arena" where the life simuation takes place. Initially the grid is empty, meaning that there are no live cells in the system. Each cell is either alive or dead, with live cells showing a green ball. Preselected patterns of live cells can be chosen from the "Pattern" drop-down list. Alternatively, individual cells can be doubled-clicked to toggle them between live and dead. It's important to understand that each cell is related to its neighboring cells, which is fundamental for the game's rules. Neighbors include not only cells to the left, right, top and bottom but also cells that are connected diagonally, so that each cell has a total of 8 neighbors. Exceptions are the four corner cells which have only three neighbors, and the cells along the four border, with five neighbors each.


So what are the basic rules that govern this game? Its goal is to show the development of a population, generation by generation. Each generation results from the preceding one, based on the simultaneous evaluation of all cells. This is the simple set of rules that govern what the next generation will look like:

  • If a live cell has fewer than 2 live neighbors, it dies of loneliness.

  • If a live cell has more than 3 live neighbors, it dies from overcrowding.

  • If a dead cell has exactly 3 live neighbors, it comes to life.

That is all there is to it. Any cell that doesn't meet any of those criteria is left as is for the next generation. With those simple rules in mind, go back and play with the system a little bit more and step through some generations, one at a time, and notice these rules taking their effect.

The screenshot below shows an example generation, with a number of live cells. Don't worry about matching the exact patterns represented in the screen shot. Just get some groups of cells added to the grid. Once you have groups of live cells in the grid, or select a pre-designed pattern, click the "Next Generation" button and notice what happens. Some of the live cells are killed (the green ball disappears) and some dead cells come to life (a green ball appears). Step through several generations and see if you notice any patterns. If you click on the "Start" button, the system will evolve itself so you don't need to click the "Next Generation" button over and over. Play with the system a little and then come back here for more details of how the application works.


Now lets delve into the code. As this is an advanced example we'll assume that by now you know your way around the Drools framework and are able to connect the presented highlight, so that we'll just focus at a high level overview. The example has two ways to execute, one way uses Agenda Groups to manage execution flow, and the other one uses Rule Flow Groups to manage execution flow. These two versions are implemented in ConwayAgendaGroupRun and ConwayRuleFlowGroupRun, respectively. Here, we'll discuss the Rule Flow version, as it's what most people will use.

All the Cell objects are inserted into the Session and the rules in the ruleflow-group "register neighbor" are allowed to execute by the Rule Flow process. This group of four rules creates Neighbor relations between some cell and its northeastern, northern, northwestern and western neighbors. This relation is bidirectional, which takes care of the other four directions. Border cells don't need any special treatment - they simply won't be paired with neighboring cells where there isn't any. By the time all activations have fired for these rules, all cells are related to all their neighboring cells.


Once all the cells are inserted, some Java code applies the pattern to the grid, setting certain cells to Live. Then, when the user clicks "Start" or "Next Generation", it executes the "Generation" ruleflow. This ruleflow is responsible for the management of all changes of cells in each generation cycle.


The rule flow process first enters the "evaluate" group, which means that any active rule in the group can fire. The rules in this group apply the Game-of-Life rules discussed in the beginning of the example, determining the cells to be killed and the ones to be given life. We use the "phase" attribute to drive the reasoning of the Cell by specific groups of rules; typically the phase is tied to a Rule Flow Group in the Rule Flow process definition. Notice that it doesn't actually change the state of any Cell objectss at this point; this is because it's evaluating the grid in turn and it must complete the full evaluation until those changes can be applied. To achieve this, it sets the cell to a "phase" which is either Phase.KILL or Phase.BIRTH, used later to control actions applied to the Cell object.


Once all Cell objects in the grid have been evaluated, we first clear any calculation activations that occured from any previous data changes. This is done via the "reset calculate" rule, which clears any activations in the "calculate" group. We then enter a split in the rule flow which allows any activations in both the "kill" and the "birth" group to fire. These rules are responsible for applying the state change.


At this stage, a number of Cell objects have been modified with the state changed to either LIVE or DEAD. Now we get to see the power of the Neighbor facts defining the cell relations. When a cell becomes live or dead, we use the Neighbor relation to iterate over all surrounding cells, increasing or decreasing the liveNeighbor count. Any cell that has its count changed is also set to to the EVALUATE phase, to make sure it is included in the reasoning during the evaluation stage of the Rule Flow Process. Notice that we don't have to do any iteration ourselves; simply by applying the relations in the rules we make the rule engine do all the hard work for us, with a minimal amount of code. Once the live count has been determined and set for all cells, the Rule Flow Process comes to and end. If the user has initially clicked the "Start" button, the engine will restart the rule flow; otherwise the user may request another generation.