issue #88 : add InferenceRulesEngine implementation
parent
0267ba397c
commit
7689e3a14d
@ -0,0 +1,74 @@
|
||||
package org.jeasy.rules.core;
|
||||
|
||||
import org.jeasy.rules.api.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
/**
|
||||
* Inference {@link RulesEngine} implementation.
|
||||
*
|
||||
* Rules are selected based on given facts and fired according to their natural order which is priority by default.
|
||||
*
|
||||
* The engine continuously select and fire rules until no more rules are applicable.
|
||||
*
|
||||
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
|
||||
*/
|
||||
public class InferenceRulesEngine implements RulesEngine {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(InferenceRulesEngine.class);
|
||||
|
||||
private RulesEngineParameters parameters;
|
||||
private List<RuleListener> ruleListeners;
|
||||
private DefaultRulesEngine delegate;
|
||||
|
||||
public InferenceRulesEngine() {
|
||||
this(new RulesEngineParameters());
|
||||
}
|
||||
|
||||
public InferenceRulesEngine(RulesEngineParameters parameters) {
|
||||
this(parameters, new ArrayList<RuleListener>());
|
||||
}
|
||||
|
||||
public InferenceRulesEngine(RulesEngineParameters parameters, List<RuleListener> ruleListeners) {
|
||||
this.parameters = parameters;
|
||||
this.ruleListeners = ruleListeners;
|
||||
delegate = new DefaultRulesEngine(parameters, ruleListeners);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RulesEngineParameters getParameters() {
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<RuleListener> getRuleListeners() {
|
||||
return ruleListeners;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fire(Rules rules, Facts facts) {
|
||||
Set<Rule> selectedRules;
|
||||
do {
|
||||
LOGGER.info("Selecting candidate rules based on the following {}", facts);
|
||||
selectedRules = selectCandidates(rules, facts);
|
||||
delegate.apply(new Rules(selectedRules), facts);
|
||||
} while (!selectedRules.isEmpty());
|
||||
}
|
||||
|
||||
private Set<Rule> selectCandidates(Rules rules, Facts facts) {
|
||||
Set<Rule> candidates = new TreeSet<>();
|
||||
for (Rule rule : rules) {
|
||||
if (rule.evaluate(facts)) {
|
||||
candidates.add(rule);
|
||||
}
|
||||
}
|
||||
return candidates;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<Rule, Boolean> check(Rules rules, Facts facts) {
|
||||
return delegate.check(rules, facts);
|
||||
}
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package org.jeasy.rules.core;
|
||||
|
||||
import org.jeasy.rules.annotation.*;
|
||||
import org.jeasy.rules.api.Facts;
|
||||
import org.jeasy.rules.api.Rules;
|
||||
import org.jeasy.rules.api.RulesEngine;
|
||||
import org.junit.Test;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
public class InferenceRulesEngineTest {
|
||||
|
||||
@Test
|
||||
public void testCandidateSelection() throws Exception {
|
||||
// Given
|
||||
Facts facts = new Facts();
|
||||
facts.put("foo", true);
|
||||
DummyRule dummyRule = new DummyRule();
|
||||
AnotherDummyRule anotherDummyRule = new AnotherDummyRule();
|
||||
Rules rules = new Rules(dummyRule, anotherDummyRule);
|
||||
RulesEngine rulesEngine = new InferenceRulesEngine();
|
||||
|
||||
// When
|
||||
rulesEngine.fire(rules, facts);
|
||||
|
||||
// Then
|
||||
assertThat(dummyRule.isExecuted()).isTrue();
|
||||
assertThat(anotherDummyRule.isExecuted()).isFalse();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCandidateOrdering() throws Exception {
|
||||
// Given
|
||||
Facts facts = new Facts();
|
||||
facts.put("foo", true);
|
||||
facts.put("bar", true);
|
||||
DummyRule dummyRule = new DummyRule();
|
||||
AnotherDummyRule anotherDummyRule = new AnotherDummyRule();
|
||||
Rules rules = new Rules(dummyRule, anotherDummyRule);
|
||||
RulesEngine rulesEngine = new InferenceRulesEngine();
|
||||
|
||||
// When
|
||||
rulesEngine.fire(rules, facts);
|
||||
|
||||
// Then
|
||||
assertThat(dummyRule.isExecuted()).isTrue();
|
||||
assertThat(anotherDummyRule.isExecuted()).isTrue();
|
||||
assertThat(dummyRule.getTimestamp()).isLessThanOrEqualTo(anotherDummyRule.getTimestamp());
|
||||
}
|
||||
|
||||
@Rule
|
||||
class DummyRule {
|
||||
|
||||
private boolean isExecuted;
|
||||
private long timestamp;
|
||||
|
||||
@Condition
|
||||
public boolean when(@Fact("foo") boolean foo) {
|
||||
return foo;
|
||||
}
|
||||
|
||||
@Action
|
||||
public void then(Facts facts) {
|
||||
isExecuted = true;
|
||||
timestamp = System.currentTimeMillis();
|
||||
facts.remove("foo");
|
||||
}
|
||||
|
||||
@Priority
|
||||
public int priority() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
public boolean isExecuted() {
|
||||
return isExecuted;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
@Rule
|
||||
class AnotherDummyRule {
|
||||
|
||||
private boolean isExecuted;
|
||||
private long timestamp;
|
||||
|
||||
@Condition
|
||||
public boolean when(@Fact("bar") boolean bar) {
|
||||
return bar;
|
||||
}
|
||||
|
||||
@Action
|
||||
public void then(Facts facts) {
|
||||
isExecuted = true;
|
||||
timestamp = System.currentTimeMillis();
|
||||
facts.remove("bar");
|
||||
}
|
||||
|
||||
@Priority
|
||||
public int priority() {
|
||||
return 2;
|
||||
}
|
||||
|
||||
public boolean isExecuted() {
|
||||
return isExecuted;
|
||||
}
|
||||
|
||||
public long getTimestamp() {
|
||||
return timestamp;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in New Issue