Bootstrap

案例研究之聊聊 QLExpress 源码 (七)

规则拆解为行为,条件(节点,类型),管理器,结果, 如果我们要实现规则的话可以借鉴其拆解结构,进行节点规则管理。

七、规则模块(rule)

7.1、Action(动作行为)

package com.ql.util.express.rule;

/**
 * 节点的动作
 * Created by tianqiao on 16/12/12.
 */
public class Action extends Node{

    public Action(String text) {
        this.setText(text);
    }
}

7.2、Condition(节点条件)

package com.ql.util.express.rule;

import org.apache.commons.lang.StringUtils;

import java.util.ArrayList;
import java.util.List;

/**
 * 节点条件
 * Created by tianqiao on 16/12/6.
 */

public class Condition extends Node{

    /**
     * 是否优先
     */
    private boolean prior = false;
    /**
     * 条件类型
     */
    private ConditionType type;
    /**
     * 子条件集合
     */
    private List children;

    public Condition(ConditionType type) {
        this.type = type;
    }

    public Condition() {
    }

    public boolean isPrior() {
        return prior;
    }

    public void setPrior(boolean prior) {
        this.prior = prior;
    }

    public ConditionType getType() {
        return type;
    }

    public void setType(ConditionType type) {
        this.type = type;
    }

    public List getChildren() {
        return children;
    }

    public void setChildren(List children) {
        this.children = children;
    }

    public void addChild(Condition child) {
        if(this.children==null){
            this.children = new ArrayList();
        }
        this.children.add(child);
    }

    @Override
    public String toString()
    {
        if(this.type == ConditionType.Leaf){
            return priorString(this.getText());
        } else if(this.type == ConditionType.And){
            return priorString(StringUtils.join(this.getChildren()," and "));
        } else if(this.type == ConditionType.Or){
            return priorString(StringUtils.join(this.getChildren()," or "));
        }
        return null;
    }

    private String priorString(String orig)
    {
        if(this.isPrior()){
            return new StringBuilder("(").append(orig).append(")").toString();
        }
        return orig;

    }
}

7.3、ConditionType(条件类型)

package com.ql.util.express.rule;

/**
 * 条件类型
 * Created by tianqiao on 16/12/6.
 */
public enum ConditionType {

    And,
    Or,
    Leaf
}

7.4、Node(节点)

package com.ql.util.express.rule;

/**
 * 节点
 * Created by tianqiao on 16/12/12.
 */
public class Node {
    /**
     * 节点ID
     */
    private Integer nodeId;
    /**
     * 级别
     */
    private Integer level;
    /**
     * 文本信息
     */
    private String text;

    public Integer getNodeId() {
        return nodeId;
    }
    public void setNodeId(Integer nodeId) {
        this.nodeId = nodeId;
    }

    public Integer getLevel() {
        return level;
    }

    public void setLevel(Integer level) {
        this.level = level;
    }

    public String getText() {
        return text;
    }

    public void setText(String text) {
        this.text = text;
    }
}

7.5、Rule(节点规则)

package com.ql.util.express.rule;

import java.util.ArrayList;
import java.util.List;

/**
 * 节点规则
 * Created by tianqiao on 16/12/8.
 */
public class Rule extends Node{

    /**
     * 代码
     */
    private String code;
    /**
     * 名称
     */
    private String name;

    public String getCode() {
        return code;
    }

    public void setCode(String code) {
        this.code = code;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * 规则场景集合
     */
    private List ruleCases = new ArrayList();

    /**
     * 获取规则案例集合
     * @return
     */
    public List getRuleCases() {
        return ruleCases;
    }

    /**
     * 添加规则案例集合
     * @param ruleCase
     */
    public void addRuleCases(RuleCase ruleCase) {
        this.ruleCases.add(ruleCase);
    }

    public String toQl()
    {
        StringBuilder sb = new StringBuilder();
        boolean first = true;
        for(RuleCase oneCase : ruleCases){
            if(first){
                sb.append(this.ruleCaseToScript("ql",oneCase));
                first = false;
            }else{
                sb.append("else ").append(this.ruleCaseToScript("ql",oneCase));
            }
        }
        return sb.toString();
    }

    /**
     * 转为天窗
     * @return
     */
    public String toSkylight()
    {
        StringBuilder sb = new StringBuilder();
        if(code!=null && name!=null){
            sb.append(String.format("rule '%s'\nname '%s'\n",code,name));
        }
        boolean first = true;
        for(RuleCase oneCase : ruleCases){
            if(first){
                sb.append(this.ruleCaseToScript("skylight",oneCase));
                first = false;
            }else{
                sb.append(this.ruleCaseToScript("skylight",oneCase));
            }
        }
        return sb.toString();
    }

    /**
     * 转为树
     * @return
     */
    public String toTree()
    {
        StringBuilder sb = new StringBuilder();
        sb.append(String.format("Rule:%s(%s),%d\n",code,name,getNodeId()));
        for(RuleCase ruleCase : ruleCases){
            printLevel(ruleCase.getLevel(),sb);
            sb.append(String.format("Case:%s,%d\n","case",ruleCase.getNodeId()));
            printConitionTree(ruleCase.getCondition(),sb);
            for(Action action:ruleCase.getActions()){
                printLevel(action.getLevel(),sb);
                sb.append(String.format("Action:%s,%d\n",action.getText(),action.getNodeId()));
            }
        }
        return sb.toString();
    }

    /**
     * 打印级别
     * @param level
     * @param sb
     */
    public void printLevel(int level,StringBuilder sb)
    {
        for(int i=0;i

7.6、RuleCase(规则案例)

package com.ql.util.express.rule;

import java.util.List;

/**
 * Created by tianqiao on 16/12/6.
 */
public class RuleCase extends Node{
    /**
     * 行为集合
     */
    private List actions;
    /**
     * 条件
     */
    private Condition condition;

    public RuleCase(Condition condition,List actions) {
        this.actions = actions;
        this.condition = condition;
    }

    public List getActions() {
        return actions;
    }

    public void setActions(List actions) {
        this.actions = actions;
    }

    public Condition getCondition() {
        return condition;
    }

    public void setCondition(Condition condition) {
        this.condition = condition;
    }
}

7.7、RuleManager(规则管理)

package com.ql.util.express.rule;

import com.ql.util.express.ExpressRunner;
import com.ql.util.express.IExpressContext;
import com.ql.util.express.parse.ExpressNode;
import com.ql.util.express.parse.Word;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by tianqiao on 16/12/8.
 */
public class RuleManager {
    
    private static final Log log = LogFactory.getLog(RuleManager.class);

    /**
     * 执行规则
     * @param runner
     * @param rule
     * @param context
     * @param isCache
     * @param isTrace
     * @return
     */
    public static RuleResult executeRule(ExpressRunner runner,Rule rule, IExpressContext context, boolean isCache, boolean isTrace)
    {
        RuleResult result = new RuleResult();
        result.setRule(rule);
        Map traceMap = new LinkedHashMap();
        result.setTraceMap(traceMap);
        Object actionResult = null;
        for(RuleCase ruleCase : rule.getRuleCases())
        {
            Condition root = ruleCase.getCondition();
            Boolean conditionResult = calculateCondition(runner,context,root,traceMap,isCache,isTrace,result);
            if(conditionResult==true){
                for(Action action :ruleCase.getActions()){
                    try {
                        traceMap.put(action.getNodeId()+"",true);
                        actionResult = runner.execute(action.getText(),context,null,isCache,isTrace);
                    } catch (Exception e) {
                        result.setHasException(true);
                        log.error("执行action出错:action=\n"+action.getText(),e);
                        actionResult = null;
                    }
                }
                break;
            }
        }
        result.setResult(actionResult);
        return result;
    }

    /**
     * 计算条件
     * @param runner
     * @param context
     * @param root
     * @param traceMap
     * @param isCache
     * @param isTrace
     * @param result
     * @return
     */
    private static Boolean calculateCondition(ExpressRunner runner, IExpressContext context, Condition root, Map traceMap, boolean isCache, boolean isTrace, RuleResult result)
    {
        boolean isShortCircuit = runner.isShortCircuit();
        String key = root.getNodeId()+"";
        if(root.getType()==ConditionType.Leaf){
            String text = root.getText();
            try {
                Boolean r = (Boolean) runner.execute(text,context,null,isCache,isTrace);
                traceMap.put(key,r);
                return r;
            } catch (Exception e) {
                result.setHasException(true);
                log.error("计算condition出错:condition=\n"+text,e);
                traceMap.put(key,false);
                return false;
            }
        }


        Boolean unionLogicResult = null;
        ConditionType rootType = root.getType();
        if(root.getChildren()!=null) {
            for (Condition sub : root.getChildren()) {
                Boolean subResult = calculateCondition(runner,context,sub,traceMap,isCache,isTrace, result);
                if(unionLogicResult==null){
                    unionLogicResult = subResult;
                }else{
                    if (rootType == ConditionType.And) {
                        unionLogicResult = unionLogicResult && subResult;
                    }else if (rootType == ConditionType.Or) {
                        unionLogicResult = unionLogicResult || subResult;
                    }
                }
                if(isShortCircuit) {
                    if (rootType == ConditionType.And) {
                        if(unionLogicResult==false){
                            break;
                        }
                    }
                    if (rootType == ConditionType.Or) {
                        if(unionLogicResult==true){
                            break;
                        }
                    }
                }
            }
        }
        traceMap.put(key,unionLogicResult);
        return unionLogicResult;
    }

    /**
     * 创建规则
     * @param root
     * @param words
     * @return
     */
    public static Rule createRule(ExpressNode root, Word[] words) {
        ExpressNode ifNode = getIfRootNode(root);
        if (ifNode != null) {
            Rule rule = new Rule();
            addRuleCaseByExpress(ifNode, rule, words);
            tagRuleConitionId(rule);
            return rule;
        }
        return null;
    }

    /**
     * 标签规则添加条件ID
     * @param rule
     * @return
     */
    private static Integer tagRuleConitionId(Rule rule) {
        Integer nodeId = 0;
        Integer level = 0;
        rule.setNodeId(nodeId++);
        rule.setLevel(level++);
        for(RuleCase ruleCase: rule.getRuleCases()){
            ruleCase.setNodeId(nodeId++);
            ruleCase.setLevel(level);
            Condition root = ruleCase.getCondition();
            nodeId = tagConditionNode(root,nodeId,level+1);
            Listactions = ruleCase.getActions();
            for(Action action:actions){
                action.setLevel(level+1);
                action.setNodeId(nodeId++);
            }
        }
        return nodeId;
    }

    /**
     * 标签条件节点
     * @param root
     * @param nodeId
     * @param level
     * @return
     */
    private static Integer tagConditionNode(Condition root,Integer nodeId,Integer level) {
        root.setLevel(level);
        root.setNodeId(nodeId++);
        if(root.getChildren()!=null) {
            for (Condition sub : root.getChildren()) {
                nodeId = tagConditionNode(sub, nodeId,level+1);
            }
        }
        return nodeId;
    }

    /**
     * 根据表达式添加规则案例
     * @param ifNode
     * @param rule
     * @param words
     */
    private static void addRuleCaseByExpress(ExpressNode ifNode, Rule rule, Word[] words) {
        ExpressNode[] children = ifNode.getChildren();
        ExpressNode condtion = null;
        ExpressNode action = null;
        ExpressNode nextCase = null;

        //[0]ConditionNode
        int point = 0;
        ExpressNode first = children[point];
        if (isNodeType(first, "CHILD_EXPRESS")) {
            condtion = first.getChildren()[0];
        } else {
            condtion = first;
        }
        point++;

        //[1]"then"
        if (isNodeType(children[point], "then")) {
            point++;
        }

        //[2]ActionNode
        action = children[point];
        point++;

        //[3]"else"
        if (point < children.length && isNodeType(children[point], "else")) {
            point++;

            //[4]IfNode
            if (point < children.length && isNodeType(children[point], "if")) {
                nextCase = children[point];
            }
        }
        rule.addRuleCases(createRuleCase(condtion, action, words));
        if (nextCase != null) {
            addRuleCaseByExpress(nextCase, rule, words);
        }
    }

    /**
     * 获取根节点
     * @param parent
     * @return
     */
    private static ExpressNode getIfRootNode(ExpressNode parent) {
        if (isNodeType(parent, "if")) {
            return parent;
        }
        ExpressNode[] children = parent.getChildren();
        if (children != null && children.length > 0) {
            for (ExpressNode child : children) {
                if (getIfRootNode(child) != null) {
                    return child;
                }
            }
        }
        return null;
    }

    /**
     * 创建规则案例
     * @param condition
     * @param action
     * @param words
     * @return
     */
    private static RuleCase createRuleCase(ExpressNode condition, ExpressNode action, Word[] words) {

        Condition ruleCondition = new Condition();
        transferCondition(condition, ruleCondition, words);
        List actions = new ArrayList();
        if (isNodeType(action, "STAT_BLOCK")) {
            ExpressNode[] children = action.getChildren();
            for (ExpressNode actionChild : children) {
                actions.add(new Action(makeActionString(actionChild,words)));
            }
        } else {
            actions.add(new Action(makeActionString(action,words)));
        }
        RuleCase ruleCase = new RuleCase(ruleCondition, actions);
        return ruleCase;
    }

    /**
     * 翻译条件
     * @param express
     * @param condition
     * @param words
     */
    private static void transferCondition(ExpressNode express, Condition condition, Word[] words) {
        if (isNodeType(express, "&&")) {
            condition.setType(ConditionType.And);
            condition.setText("and");
            ExpressNode[] children = express.getChildren();
            for (ExpressNode child : children) {
                Condition subCondition = new Condition();
                condition.addChild(subCondition);
                transferCondition(child, subCondition, words);
            }

        } else if (isNodeType(express, "||")) {
            condition.setType(ConditionType.Or);
            condition.setText("or");
            ExpressNode[] children = express.getChildren();
            for (ExpressNode child : children) {
                Condition subCondition = new Condition();
                condition.addChild(subCondition);
                transferCondition(child, subCondition, words);
            }
        } else {
            if (isNodeType(express, "CHILD_EXPRESS")||isNodeType(express, "STAT_BLOCK")||isNodeType(express, "STAT_SEMICOLON")) {            //注意括号的情况
                ExpressNode realExpress = express.getChildren()[0];
                condition.setPrior(true);
                transferCondition(realExpress, condition, words);
            } else {
                condition.setType(ConditionType.Leaf);
                condition.setText(makeCondtionString(express, words));

            }
        }
    }

    /**
     * 是否是节点类型
     * @param node
     * @param type
     * @return
     */
    private static boolean isNodeType(ExpressNode node, String type) {
        return node.getNodeType().getName().equals(type);

    }

    /**
     * 匹配行为
     * @param express
     * @param words
     * @return
     */
    private static String makeActionString(ExpressNode express, Word[] words)
    {
        int min = getMinNode(express);
        int max = getMaxNode(express);
        //最后需要匹配一个)括号的问题,另外还有是无参数的情况 function()
        while(max+1=words.length) max = words.length-1;
        StringBuilder result = new StringBuilder();
        int balance = 0;//小括号的相互匹配数量
        for(int i=min;i<=max;i++)
        {
            if(words[i].word.equals("(")){
                balance++;
            }else if(words[i].word.equals(")")){
                balance--;
            }
            if(balance<0){
                balance++;//当前字符不合并,恢复成0,用于最终的判断
                break;
            }
            result.append(words[i].word);
            if(words[i].word.equals("return")){
                result.append(" ");
            }
        }
        if(balance!=0){
            System.out.println(result);
            throw new RuntimeException("括号匹配异常");
        }
        return result.toString();
    }

    /**
     * 匹配条件
     * @param express
     * @param words
     * @return
     */
    private static String makeCondtionString(ExpressNode express,Word[] words)
    {
        int min = getMinNode(express);
        int max = getMaxNode(express);
        //最后需要匹配一个括号的问题
        while(max+1=words.length) max = words.length-1;
        StringBuilder result = new StringBuilder();
        int balance = 0;//小括号的相互匹配数量
        for(int i=min;i<=max;i++)
        {
            if(words[i].word.equals("(")){
                balance++;
            }else if(words[i].word.equals(")")){
                balance--;
            }
            if(balance<0){
                balance++;//当前字符不合并,恢复成0,用于最终的判断
                break;
            }
            result.append(words[i].word);
        }
        if(balance!=0){
            throw new RuntimeException("括号匹配异常");
        }
        return result.toString();
    }

    /**
     * 获取最小的节点
     * @param express
     * @return
     */
    private static int getMinNode(ExpressNode express)
    {
        if(express.getChildren()==null||express.getChildren().length==0){
            return express.getWordIndex();
        }
        int wordIndex = express.getWordIndex();
        if(express.getChildren()!=null){
            for(ExpressNode child : express.getChildren()){
                int childIndex = getMinNode(child);
                if(wordIndex==-1 || childIndexwordIndex){
                    wordIndex = childIndex;
                }
            }
        }
        return wordIndex;
    }

    /**
     * 创建条件
     * @param condition
     * @param words
     * @return
     */
    public static Condition createCondition(ExpressNode condition, Word[] words) {
        Condition ruleCondition = new Condition();
        transferCondition(condition, ruleCondition, words);
        return ruleCondition;
    }
    
}

7.8、RuleResult(规则结果)

package com.ql.util.express.rule;

import java.util.Map;

/**
 * 规则结果
 * Created by tianqiao on 16/12/12.
 */
public class RuleResult {
    /**
     * 是否有异常
     */
    private boolean hasException = false;
    /**
     * 规则
     */
    private Rule rule;
    /**
     * 轨迹集合
     */
    private Map traceMap;
    /**
     * 结果对象
     */
    private Object result;
    /**
     * 脚本
     */
    private String script;
    
    public String getScript() {
        return script;
    }
    
    public void setScript(String script) {
        this.script = script;
    }
    
    public boolean isHasException() {
        return hasException;
    }
    
    public void setHasException(boolean hasException) {
        this.hasException = hasException;
    }
    
    public Rule getRule() {
        return rule;
    }

    public void setRule(Rule rule) {
        this.rule = rule;
    }

    public Map getTraceMap() {
        return traceMap;
    }

    public void setTraceMap(Map traceMap) {
        this.traceMap = traceMap;
    }

    public Object getResult() {
        return result;
    }

    public void setResult(Object result) {
        this.result = result;
    }
}

7.9、小结

  • 规则拆解为行为,条件(节点,类型),管理器,结果