Table of contents
Abstraction of logical expressions and conditions under rules
For any rule, including multiple conditions, of course, it can be abstracted into the following json form:
Among them, 1, 2, and 3 respectively represent 3 conditions; how this rule is implemented is "1 & (2 | 3)", of course, it can be in a similar form. The following json definition
{
"logic": "1 & (2 | 3)",
"conditions":[
{
"isNot":true,
"itemId":1,
"left":{
"value":"1",
"isVar":true
},
"operator":"<",
"right":{
"value":"3",
"isVar":true
}
},
{
"isNot":false,
"itemId":2,
"left":{
"value":"1",
"isVar":true
},
"operator":"==",
"right":{
"value":"3",
"isVar":true
}
},
{
"isNot":false,
"itemId":3,
"left":{
"value":"number",
"isVar":true
},
"operator":"startsWith",
"right":{
"value":"666",
"isVar":true
}
}
]
}
The abstraction of each condition includes the corresponding ID , which is used to associate logical expressions (ie 1, 2, 3), and the three elements of the condition: left variable , right variable , operator
{
"isNot":false,
"itemId":1,
"left":{
"value":"1",
"isVar":true
},
"operator":"<",
"right":{
"value":"3",
"isVar":true
}
}
Encoding and Abstraction of Expressive Logic
The processing of logical expressions 1 & (2 | 3)
is still similar to suffix expressions: 2 | 3 executes the result of & with 1. 1 & (2 | 3)
After parsing is as follows:
Operator expression: contains operation data and operators , defined as follows
public class OperatorExpr implements Expr {
// 操作数列表
private List<Expr> operands;
// 操作符的名称
private String operator;
/**
* !标志,not在表达式和操作符中都可能出现,用这个key来区分这两种情况,主要用于统计分析
*
* 其余取值无意义
*/
private String notToken;
/**
* 表达式的标识,不是所有的Operator都有
*/
private int id = -1;
public OperatorExpr(String operator, List<Expr> operands) {
this(operator, operands, "");
}
public OperatorExpr(String operator, List<Expr> operands, String notToken) {
this.operator = operator;
this.operands = operands;
this.notToken = notToken;
}
public List<Expr> getOperands() {
return operands;
}
public void setOperands(List<Expr> operands) {
this.operands = operands;
}
public String getOperator() {
return operator;
}
public void setOperator(String operator) {
this.operator = operator;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
@Override
public void accept(ExprVisitor visitor) {
visitor.visit(this);
}
}
rule
conditions under the rules
public class Condition {
/**
* 序号。
*
* 一般从 1 开始。
*/
private int itemId;
/**
* 操作符。
*
* 比如 : 大于,不等于 等
*/
private String operator;
/**
*
*/
private boolean isNot;
/**
* 左变量
*/
private Operand left;
/**
* 右变量
*/
private Operand right;
operator abstraction
public abstract class AbstractBaseOperator extends AbstractIdentifiableOperator{
/**
* @param context 引擎执行上下文
* @return 识别结果
*/
@Override
public EvalResult doEval(EngineExecutionContext context) {
Variable op1 = operands.get(0);
Variable op2 = operands.get(1);
if (op1 == null || op2 == null) {
throw new IllegalArgumentException("argument in operator can't be null.");
}
Object a, b = null;
a = op1.eval(context);
//op2.eval()是个耗时操作。如果op1是Unknown,op2.eval就没必要做了。
if (a == EvalResult.Unknown) {
return EvalResult.Unknown;
}
if (op2 != null) {
b = op2.eval(context);
}
if (b == EvalResult.Unknown) {
return EvalResult.Unknown;
}
return invokeOperator(a, b, context);
}
private EvalResult invokeOperator(Object a, Object b, EngineExecutionContext context) {
try {
// 第一遍求值时,忽略高耗时操作符
return EvalResult.valueOf(apply(a, b, context));
} catch (Throwable e) {
throw e;
} finally {
}
}
/**
* 操作符具体逻辑
*
* @param a 左操作实参
* @param b 右操作实参
* @param context 上下文
* @return true/false
*/
protected abstract Boolean apply(Object a, Object b, EngineExecutionContext context);
@Override
public void accept(EvaluableVisitor visitor) {
visitor.visit(this);
}
}
- The logic of taking and evaluating the left and right operands is required, the eval method
- Perform operator operations, accept method
The reason why the interface is used is mainly to implement delayed operation or time-consuming operation, that is, to execute when it needs to be executed. Please refer to the review: https://doctording.blog.csdn.net/article/details/121593411
define rule class
public class Rule implements Serializable, Evaluable<RuleResult>{
protected String id;
protected String title;
private RuleEntry ruleEntry;
protected volatile Evaluable<EvalResult> expression;
public Rule() {
}
public Rule(RuleEntry entry) {
this.id = entry.getId();
this.expression = parse(entry);
this.ruleEntry = entry;
}
@Override
public RuleResult eval(EngineExecutionContext context) {
long startTime = System.nanoTime();
RuleResult result = new RuleResult(EvalResult.False, this);
try {
EvalResult evalResult = getExpression().eval(context);
result.setEvalResult(evalResult);
} catch (Exception ab) {
result = new RuleResult(ab);
} finally {
result.setCost(System.nanoTime() - startTime);
}
return result;
}
@Override
public void accept(EvaluableVisitor visitor) {
visitor.visit(this);
}
//
public Evaluable<EvalResult> getExpression() {
return expression;
}
// parse表达式并执行
public Evaluable<EvalResult> parse(RuleEntry rawRule) {
try {
Expr expr = parseExpr(rawRule.getExpression());
return toEvaluable(rawRule, expr);
} catch (Exception e) {
throw new IllegalArgumentException("parse rule error, ruleId: " + rawRule.getId(), e);
}
}
public Expr parseExpr(Expression rawExpr) {
if (rawExpr == null) {
throw new IllegalArgumentException("absence of raw expression");
}
return parseExpr(rawExpr.getLogic(), rawExpr.getConditions());
}
public Expr parseExpr(String logic, List<Condition> conditions) {
ExprParser parser = new ExprParser(logic, conditions);
return parser.expr();
}
public static Evaluable<EvalResult> toEvaluable(RuleEntry rawRule, Expr expr) {
if (expr == null) {
return null;
}
String ruleId = rawRule.getId();
EvalExprVisitor visitor = new EvalExprVisitor();
expr.accept(visitor);
return visitor.getEvalExpr();
}
}
- Rule properties contain conditional expressions and their operation logic
- When the rule is initialized, the conditional expression is initialized and verified.
- The execution of rules is
1 & (2 | 3)
the process of executing logical expressions
rule enforcement
public class RuleTest {
public static void main(String[] args) {
String logic = "1 &(2|3)";
List<Condition > conditions = new ArrayList<>();
Condition condition1 = new Condition();
condition1.setItemId(1);
Operand operandLeft = new Operand();
operandLeft.setIsVar(true);
operandLeft.setValue("age");
operandLeft.setModifier("age");
condition1.setLeft(operandLeft);
condition1.setOperator(Operator.GT);
Operand operandRight = new Operand();
operandRight.setIsVar(false);
operandRight.setValue("18");
operandRight.setType("int");
condition1.setRight(operandRight);
conditions.add(condition1);
Condition condition2 = new Condition();
condition2.setItemId(2);
Operand operandLeft2 = new Operand();
operandLeft2.setIsVar(false);
operandLeft2.setValue("2");
condition2.setLeft(operandLeft2);
condition2.setOperator(Operator.LT);
Operand operandRight2 = new Operand();
operandRight2.setIsVar(false);
operandRight2.setValue("1");
condition2.setRight(operandRight2);
conditions.add(condition2);
Condition condition3 = new Condition();
condition3.setItemId(3);
Operand operandLeft3 = new Operand();
operandLeft3.setIsVar(true);
operandLeft3.setValue("number");
operandLeft3.setModifier("number");
condition3.setLeft(operandLeft3);
condition3.setOperator(Operator.CONTAINS_STRING);
Operand operandRight3 = new Operand();
operandRight3.setIsVar(false);
operandRight3.setValue("666");
condition3.setRight(operandRight3);
conditions.add(condition3);
Expression expression = new Expression(logic, conditions);
RuleEntry ruleEntry = new RuleEntry();
ruleEntry.setId("1");
ruleEntry.setExpression(expression);
// 构造规则
Rule rule = new Rule(ruleEntry);
// 构造规则执行的上下文
EngineExecutionContext engineExecutionContext = new EngineExecutionContext();
Map<String, Object> ctx = new HashMap<>();
ctx.put("age", 19);
ctx.put("number", "666abc");
engineExecutionContext.setData(ctx);
// 执行规则并打印结果
RuleResult ruleResult = rule.eval(engineExecutionContext);
System.out.println(ruleResult);
}
}
Expression traversal for operator calculation addition
public class EvalExprVisitor implements ExprVisitor {
private Operator<?, EvalResult> expr = null;
private Operator<Evaluable<?>, ?> currentParent = null;
@Override
public boolean visit(IdentifierExpr x) {
if (null != currentParent && null != x) {
currentParent.addOperand(new Identifier(x.getConditionId(),x.getName()));
}
return false;
}
@Override
public boolean visit(LiteralExpr x) {
if (null != currentParent && null != x) {
currentParent.addOperand(new Literal(x.getValue()));
}
return false;
}
@SuppressWarnings({
"unchecked", "rawtypes" })
@Override
public boolean visit(OperatorExpr x) {
String operator = x.getOperator();
// 获取操作符实际例子
Operator opexpr = OperatorLoader.getOperator(operator);
if (expr == null) {
expr = opexpr;
}else {
currentParent.addOperand(opexpr);
}
Operator<Evaluable<?>, ?> oldParent = currentParent;
currentParent = opexpr;
List<Expr> operands = x.getOperands();
if (operands != null && !operands.isEmpty()) {
for (Expr expr : operands) {
expr.accept(this);
}
}
currentParent = oldParent;
return false;
}
public Evaluable<EvalResult> getEvalExpr() {
return expr;
}
}
Different expression types: the left variable, operator, and right variable are all abstracted into an expression, and then parsed and executed during the specific execution process
public interface ExprVisitor {
/**
* 访问变量表达式
*
* @param identifierExpr
* @return
*/
boolean visit(IdentifierExpr identifierExpr);
/**
* 访问常量表达式
*
* @param literalExpr
* @return
*/
boolean visit(LiteralExpr literalExpr);
/**
* 访问操作符表达式
*
* @param operatorExpr
* @return
*/
boolean visit(OperatorExpr operatorExpr);
}
illustrate:
- Expressions can be cached before program startup by
- SPI can be opened to support user-defined expressions
Execution of a specific condition under a rule
The execution of a condition under a rule is actually the execution of an operator
- Step 1: Obtain the left and right operands, which need to be dynamically executed according to the context to obtain the final value
- Step 2: Execute the specific judgment comparison of the operator
- Step 3: Get the result, or as the operand for the execution of the next operator
public abstract class AbstractBaseOperator extends AbstractIdentifiableOperator{
/**
* @param context 引擎执行上下文
* @return 识别结果
*/
@Override
public EvalResult doEval(EngineExecutionContext context) {
Variable op1 = operands.get(0);
Variable op2 = operands.get(1);
if (op1 == null || op2 == null) {
throw new IllegalArgumentException("argument in operator can't be null.");
}
Object a, b = null;
a = op1.eval(context);
//op2.eval()是个耗时操作。如果op1是Unknown,op2.eval就没必要做了。
if (a == EvalResult.Unknown) {
return EvalResult.Unknown;
}
if (op2 != null) {
b = op2.eval(context);
}
if (b == EvalResult.Unknown) {
return EvalResult.Unknown;
}
return invokeOperator(a, b, context);
}
private EvalResult invokeOperator(Object a, Object b, EngineExecutionContext context) {
try {
// 第一遍求值时,忽略高耗时操作符
return EvalResult.valueOf(apply(a, b, context));
} catch (Throwable e) {
throw e;
} finally {
}
}
/**
* 操作符具体逻辑
*
* @param a 左操作实参
* @param b 右操作实参
* @param context 上下文
* @return true/false
*/
protected abstract Boolean apply(Object a, Object b, EngineExecutionContext context);
@Override
public void accept(EvaluableVisitor visitor) {
visitor.visit(this);
}
Get the final result after the rule is executed
Description: During the rule execution process, the execution status of each condition can be recorded: time-consuming, abnormal, access problems, etc. In the returned results, it can also be recorded to facilitate subsequent troubleshooting.