Search code examples
drools

DROOLS local variable assignment with nested members


I am having trouble assigning a local variable with nested members/objects in DROOLS 6.2 and Optaplanner with two Java classes. I am trying to determine when two facts/instances nested members have the same values. The test case below is simple, in reality I'm trying to compare multiple nested members in a rule.

public class A {
  private B x;
  //public B getX(), public void setX(B b) follow ...
}

public class B {
  private int y;
  //public int getY(), public void setY(int y) follow ...
}

rule "testnestedmembers"
when  
    A(x.y : y, $x : x)
    A(x2.y == y, $x : x2) 
then 
    scoreHolder.addHardConstraintMatch(kcontext,-1000);

    Message [id=1, level=ERROR,      path=org/somebody/project/planner/solver/planScoreRules.drl, line=16, column=0
   text=[ERR 102] Line 16:49 mismatched input ':' in rule "testnestedmembers"]
    Message [id=2, level=ERROR, path=org/somebody/project/planner/solver/planScoreRules.drl, line=0, column=0 text=Parser returned a null Package]
    ---
    Warning Messages:
    ---
    Info Messages:

    at    org.optaplanner.core.config.score.director.ScoreDirectorFactoryConfig.buildKieBase(ScoreDirectorFactoryConfig.java:387)

I have reviewed some answers, such as: Drools Rule Expression : Access Nested class data member

And Geoffrey De Smet's answer illustrates a conditional, but not a local assignment. I've tried different variations, but no luck. Thank you for any advice.

Edit: I should have said creating a binding instead of assigning a local variable.


Solution

  • First I'll point out what causes the compiler errors.

    rule "testnestedmembers"
    when  
        A(x.y : y, $x : x)     // (1)
        A(x2.y == y, $x : x2)  // (2) (3)
    

    (1) A binding has the form <variable> : <field>, but x.y isn't a valid variable name. (2) Same with x2.y. Also, x2 isn't a field in A. (3) Nothing keeps the rule engine from matching the same fact with both patterns for class A. This means that the rule will fire for each and every fact of class A, since (as you intend) A.x.y is always equal to itself.

    Correct is

    rule "testnestedmembers"
    when
        $a1: A( $x1: x )
        A( this != $a1, $x2: x, $x1.getY() == $x2.getY() )
    then
         ...
    

    However! This rule fires twice, once with one fact bound to $a1, and once with the other (matching) one bound to $a1. One possibility is to test for the existence of such a pair (or cluster!):

    rule "testnestedmembers"
    when
        $a1: A( $x1: x )
        exists A( this != $a1, $x2: x, $x1.getY() == $x2.getY() )
    then
         ...
    

    Another option is to ensure an ordering by testing an attribute:

    rule "testnestedmembers"
    when
        $a1: A( $x1: x, $id: id )
        exists A( id > $id, $x2: x, $x1.getY() == $x2.getY() )
    then
    

    Now this fires for each pair of A's where the second has a greater (unique!) id.