issue #119: Add separate concepts for conditions and actions

pull/130/merge
Mahmoud Ben Hassine 7 years ago
parent ca5eb83923
commit 4ff3f90705

@ -0,0 +1,40 @@
/**
* The MIT License
*
* Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.rules.api;
/**
* This interface represents a rule's action.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public interface Action {
/**
* Execute the action when the rule evaluates to true.
*
* @param facts known at the time of execution of the action
* @throws Exception when unable to execute the action
*/
void execute(Facts facts) throws Exception;
}

@ -0,0 +1,61 @@
/**
* The MIT License
*
* Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.rules.api;
/**
* This interface represents a rule's condition.
*
* @author Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*/
public interface Condition {
/**
* Evaluate the condition according to the known facts.
*
* @param facts known when evaluating the rule.
*
* @return true if the rule should be triggered, false otherwise
*/
boolean evaluate(Facts facts);
/**
* A NoOp {@link Condition} that always returns false.
*/
Condition FALSE = new Condition() {
@Override
public boolean evaluate(Facts facts) {
return false;
}
};
/**
* A NoOp {@link Condition} that always returns true.
*/
Condition TRUE = new Condition() {
@Override
public boolean evaluate(Facts facts) {
return true;
}
};
}

@ -0,0 +1,113 @@
/**
* The MIT License
*
* Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.rules.core;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import java.util.ArrayList;
import java.util.List;
class DefaultRule implements Rule {
private String name = Rule.DEFAULT_NAME;
private String description = Rule.DEFAULT_DESCRIPTION;
private int priority = Rule.DEFAULT_PRIORITY;
private Condition condition = Condition.FALSE;
private List<Action> actions = new ArrayList<>();
DefaultRule(String name, String description, int priority, Condition condition, List<Action> actions) {
this.name = name;
this.description = description;
this.priority = priority;
this.condition = condition;
this.actions = actions;
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public int getPriority() {
return priority;
}
@Override
public boolean evaluate(Facts facts) {
return condition.evaluate(facts);
}
@Override
public void execute(Facts facts) throws Exception {
for (Action action : actions) {
action.execute(facts);
}
}
/*
* Rules are unique according to their names within a rule set (Rules object).
*/
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null) return false;
if (!o.getClass().isAssignableFrom(Rule.class)) return false;
Rule that = (Rule) o;
return name.equals(that.getName());
}
@Override
public int hashCode() {
return name.hashCode();
}
@Override
public int compareTo(final Rule rule) {
int priorityComparisonResult = Integer.compare(priority, rule.getPriority());
if (priorityComparisonResult != 0) {
return priorityComparisonResult;
} else {
return name.compareTo(rule.getName());
}
}
@Override
public String toString() {
return name;
}
}

@ -0,0 +1,74 @@
/**
* The MIT License
*
* Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.rules.core;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Rule;
import java.util.ArrayList;
import java.util.List;
public class RuleBuilder {
private String name = Rule.DEFAULT_NAME;
private String description = Rule.DEFAULT_DESCRIPTION;
private int priority = Rule.DEFAULT_PRIORITY;
private Condition condition = Condition.FALSE;
private List<Action> actions = new ArrayList<>();
public static RuleBuilder aNewRule() {
return new RuleBuilder();
}
public RuleBuilder named(String name) {
this.name = name;
return this;
}
public RuleBuilder withDescription(String description) {
this.description = description;
return this;
}
public RuleBuilder withPriority(int priority) {
this.priority = priority;
return this;
}
public RuleBuilder when(Condition condition) {
this.condition = condition;
return this;
}
public RuleBuilder then(Action action) {
this.actions.add(action);
return this;
}
public Rule build() {
return new DefaultRule(name, description, priority, condition, actions);
}
}

@ -0,0 +1,81 @@
/**
* The MIT License
*
* Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.rules.core;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Rule;
import org.junit.Test;
import org.mockito.InOrder;
import org.mockito.Mock;
import org.mockito.Mockito;
import static org.mockito.Mockito.*;
public class DefaultRuleTest extends AbstractTest {
@Mock
private Condition condition;
@Mock
private Action action1, action2;
@Test
public void WhenConditionIsTrue_ThenActionsShouldBeExecutedInOrder() throws Exception {
// given
when(condition.evaluate(facts)).thenReturn(true);
Rule rule = RuleBuilder.aNewRule()
.when(condition)
.then(action1)
.then(action2)
.build();
rules.register(rule);
// when
rulesEngine.fire(rules, facts);
// then
InOrder inOrder = Mockito.inOrder(action1, action2);
inOrder.verify(action1).execute(facts);
inOrder.verify(action2).execute(facts);
}
@Test
public void WhenConditionIsFalse_ThenActionsShouldNotBeExecuted() throws Exception {
// given
when(condition.evaluate(facts)).thenReturn(false);
Rule rule = RuleBuilder.aNewRule()
.when(condition)
.then(action1)
.then(action2)
.build();
rules.register(rule);
// when
rulesEngine.fire(rules, facts);
// then
verify(action1, never()).execute(facts);
verify(action2, never()).execute(facts);
}
}

@ -0,0 +1,76 @@
/**
* The MIT License
*
* Copyright (c) 2017, Mahmoud Ben Hassine (mahmoud.benhassine@icloud.com)
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.jeasy.rules.core;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.junit.MockitoJUnitRunner;
import static java.util.Arrays.asList;
import static org.assertj.core.api.Assertions.assertThat;
@RunWith(MockitoJUnitRunner.class)public class RuleBuilderTest {
@Mock
private Condition condition;
@Mock
private Action action1, action2;
@Test
public void testDefaultRuleCreationWithDefaultValues() throws Exception {
// when
Rule rule = RuleBuilder.aNewRule().build();
// then
assertThat(rule.getName()).isEqualTo(Rule.DEFAULT_NAME);
assertThat(rule.getDescription()).isEqualTo(Rule.DEFAULT_DESCRIPTION);
assertThat(rule.getPriority()).isEqualTo(Rule.DEFAULT_PRIORITY);
assertThat(rule).isInstanceOf(DefaultRule.class);
}
@Test
public void testDefaultRuleCreationWithCustomValues() throws Exception {
// when
Rule rule = RuleBuilder.aNewRule()
.named("myRule")
.withDescription("myRuleDescription")
.withPriority(3)
.when(condition)
.then(action1)
.then(action2)
.build();
// then
assertThat(rule.getName()).isEqualTo("myRule");
assertThat(rule.getDescription()).isEqualTo("myRuleDescription");
assertThat(rule.getPriority()).isEqualTo(3);
assertThat(rule).isInstanceOf(DefaultRule.class);
assertThat(rule).extracting("condition").containsExactly(condition);
assertThat(rule).extracting("actions").containsExactly(asList(action1, action2));
}
}

@ -1,24 +0,0 @@
package org.jeasy.rules.tutorials.airco;
import org.jeasy.rules.annotation.Action;
import org.jeasy.rules.annotation.Condition;
import org.jeasy.rules.annotation.Fact;
import org.jeasy.rules.annotation.Rule;
import org.jeasy.rules.api.Facts;
@Rule(name = "air conditioning rule", description = "if it is hot, decrease temperature" )
public class AirConditioningRule {
@Condition
public boolean isItHot(@Fact("temperature") int temperature) {
return temperature > 25;
}
@Action
public void coolAir(Facts facts) {
System.out.println("It is hot! cooling air..");
Integer temperature = facts.get("temperature");
facts.put("temperature", temperature - 1);
}
}

@ -0,0 +1,18 @@
package org.jeasy.rules.tutorials.airco;
import org.jeasy.rules.api.Action;
import org.jeasy.rules.api.Facts;
public class DecreaseTemperatureAction implements Action {
static DecreaseTemperatureAction decreaseTemperature() {
return new DecreaseTemperatureAction();
}
@Override
public void execute(Facts facts) throws Exception {
System.out.println("It is hot! cooling air..");
Integer temperature = facts.get("temperature");
facts.put("temperature", temperature - 1);
}
}

@ -0,0 +1,18 @@
package org.jeasy.rules.tutorials.airco;
import org.jeasy.rules.api.Condition;
import org.jeasy.rules.api.Facts;
public class HighTemperatureCondition implements Condition {
static HighTemperatureCondition itIsHot() {
return new HighTemperatureCondition();
}
@Override
public boolean evaluate(Facts facts) {
Integer temperature = facts.get("temperature");
return temperature > 25;
}
}

@ -1,10 +1,15 @@
package org.jeasy.rules.tutorials.airco;
import org.jeasy.rules.api.Facts;
import org.jeasy.rules.api.Rule;
import org.jeasy.rules.api.Rules;
import org.jeasy.rules.api.RulesEngine;
import org.jeasy.rules.core.InferenceRulesEngine;
import static org.jeasy.rules.core.RuleBuilder.aNewRule;
import static org.jeasy.rules.tutorials.airco.DecreaseTemperatureAction.decreaseTemperature;
import static org.jeasy.rules.tutorials.airco.HighTemperatureCondition.itIsHot;
public class Launcher {
public static void main(String[] args) {
@ -13,7 +18,11 @@ public class Launcher {
facts.put("temperature", 30);
// define rules
AirConditioningRule airConditioningRule = new AirConditioningRule();
Rule airConditioningRule = aNewRule()
.named("air conditioning rule")
.when(itIsHot())
.then(decreaseTemperature())
.build();
Rules rules = new Rules();
rules.register(airConditioningRule);

Loading…
Cancel
Save