实现一个支持自定义函数的模板表达式

Posted lichmama

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了实现一个支持自定义函数的模板表达式相关的知识,希望对你有一定的参考价值。

工作中需要用到一个支持变量替换和自定义函数的模板表达式,发现现有的开源项目不能满足,于是自己造了个轮子。

该模板表达式核心就三个文件:

ExpressionNode.java -- 表达式节点

技术图片
public class ExpressionNode {
    /** 模板 **/
    public static final Integer TEMPLATE = 0;
    /** 函数 **/
    public static final Integer FUNCTION = 1;
    /** 变量 **/
    public static final Integer VARIABLE = 2;
    /** 常量 **/
    public static final Integer CONSTANT = 3;
    /** 类型 **/
    private Integer nodeType;
    /** 表达式 **/
    private String expression;
    /** 值 **/
    private Object value;
    /** 参数 **/
    private List<ExpressionNode> arguments;
    /** 变量值类型 **/
    private Integer varType;
    /** 默认值 **/
    private String defVal;

    public Integer getNodeType() {
        return nodeType;
    }

    public void setNodeType(Integer nodeType) {
        this.nodeType = nodeType;
    }

    public String getExpression() {
        return expression;
    }

    public void setExpression(String expression) {
        this.expression = expression;
    }

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }

    public List<ExpressionNode> getArguments() {
        return arguments;
    }

    public void setArguments(List<ExpressionNode> arguments) {
        this.arguments = arguments;
    }

    public Integer getVarType() {
        return varType;
    }

    public void setVarType(Integer varType) {
        this.varType = varType;
    }

    public String getDefVal() {
        return defVal;
    }

    public void setDefVal(String defVal) {
        this.defVal = defVal;
    }

    public void addArgument(ExpressionNode argument) {
        if (argument == null) {
            return;
        }
        if (arguments == null) {
            arguments = new ArrayList<ExpressionNode>();
        }
        arguments.add(argument);
    }

    public List<Object> getArgumentValues() {
        List<Object> values = new ArrayList<Object>();
        if (arguments != null) {
            arguments.forEach(arg -> {
                values.add(arg.getValue());
            });
        }
        return values;
    }

    @Override
    public String toString() {
        return "ExpressionNode [nodeType=" + nodeType + ", expression=" + expression + ", value=" + value
                + ", arguments=" + arguments + ", varType=" + varType + ", defVal=" + defVal + "]";
    }

}
View Code

 

ExpressionParser.java -- 表达式解析器

技术图片
public class ExpressionParser {
    public static final String EXP_FUNCTION = "\$(?<fun>[a-zA-Z0-9_]+)\((?<args>.*?)\)";
    public static final String EXP_VARIABLE = "#\{(?<var>[a-zA-Z0-9_.]+)(\:(?<type>[a-z]+))?(\?(?<def>.*?))?\}";
    private static final String COMMA = "\s*,\s*";
    private static final Integer NOT_SUPPORTED_TYPE = -1;
    private Pattern FUNCTION = Pattern.compile(EXP_FUNCTION);
    private Pattern VARIABLE = Pattern.compile(EXP_VARIABLE);
    private String expression;
    private Stack<ExpressionNode> expNodeStack = new Stack<ExpressionNode>();
    private Map<String, Integer> supportedTypes = new HashMap<>();

    public ExpressionParser(String expression) {
        this.expression = expression;
        supportedTypes.put("string", Constant.STRING);
        supportedTypes.put("boolean", Constant.BOOLEAN);
        supportedTypes.put("integer", Constant.INTEGER);
        supportedTypes.put("object", Constant.OBJECT);
        supportedTypes.put("collection", Constant.COLLECTION);
    }

    public Stack<ExpressionNode> getExpNodeStack() {
        return expNodeStack;
    }

    public void parse() {
        parse(expression);
    }

    /**
     * 解析表达式
     *
     * @param expression
     * @return
     */
    public ExpressionNode parse(String expression) {
        if (expression == null || expression.isEmpty()) {
            return null;
        }
        ExpressionNode expNode = new ExpressionNode();
        expNodeStack.push(expNode);
        if (parseFunction(expNode, expression)) {
            return expNode;
        }
        if (parseVariable(expNode, expression)) {
            return expNode;
        }
        if (parseTemplate(expNode, expression)) {
            return expNode;
        }
        expNode.setNodeType(ExpressionNode.CONSTANT);
        expNode.setExpression(expression);
        return expNode;
    }

    private boolean parseFunction(ExpressionNode expNode, String expression) {
        Matcher matcher = Pattern.compile(EXP_FUNCTION).matcher(expression);
        if (!matcher.find()) {
            return false;
        }
        if (matcher.end() - matcher.start() != expression.length()) {
            return false;
        }
        expNode.setNodeType(ExpressionNode.FUNCTION);
        String fun = matcher.group("fun");
        String args = matcher.group("args");
        expNode.setExpression(fun);
        for (String argExp : args.split(COMMA)) {
            if (!argExp.isEmpty()) {
                expNode.addArgument(parse(argExp));
            }
        }
        return true;
    }

    private boolean parseVariable(ExpressionNode expNode, String expression) {
        Matcher matcher = Pattern.compile(EXP_VARIABLE).matcher(expression);
        if (!matcher.find()) {
            return false;
        }
        if (matcher.end() - matcher.start() != expression.length()) {
            return false;
        }
        expNode.setNodeType(ExpressionNode.VARIABLE);
        String var = matcher.group("var");
        String type = matcher.group("type");
        String defVal = matcher.group("def");
        if (type == null) {
            type = "string"; // default type: string
        }
        Integer varType = getVarType(type);
        expNode.setExpression(var);
        expNode.setVarType(varType);
        expNode.setDefVal(defVal);
        return true;
    }

    /**
     *
     * @param type
     * @return
     */
    private Integer getVarType(String type) {
        if (!supportedTypes.containsKey(type)) {
            return NOT_SUPPORTED_TYPE;
        } else {
            return supportedTypes.get(type);
        }
    }

    /**
     * 解析(模板)表达式
     * 
     * @param expNode
     * @param expression
     * @return
     */
    private boolean parseTemplate(ExpressionNode expNode, String expression) {
        expNode.setNodeType(ExpressionNode.TEMPLATE);
        StringBuffer temp = new StringBuffer();
        int argNodeCount = processSubNode(expNode, 0, FUNCTION.matcher(expression), temp);
        if (argNodeCount > 0) {
            expression = temp.toString();
        }
        temp.setLength(0);
        argNodeCount = processSubNode(expNode, argNodeCount, VARIABLE.matcher(expression), temp);
        if (argNodeCount > 0) {
            expNode.setExpression(temp.toString());
        }
        return argNodeCount > 0;
    }

    /**
     * 处理子节点
     * 
     * @param expNode
     * @param argNodeCount
     * @param matcher
     * @param temp
     * @return
     */
    private int processSubNode(ExpressionNode expNode, int argNodeCount, Matcher matcher, StringBuffer temp) {
        while (matcher.find()) {
            ExpressionNode subNode = parse(matcher.group());
            if (subNode == null) {
                continue;
            }
            expNode.addArgument(subNode);
            matcher.appendReplacement(temp, "{{" + argNodeCount + "}}");
            argNodeCount++;
        }
        matcher.appendTail(temp);
        return argNodeCount;
    }

}
View Code

 

ExpressionCalculator.java -- 表达式计算器

技术图片
public class ExpressionCalculator {
    private static Logger logger = LogManager.getLogger(ExpressionCalculator.class);
    private static ConcurrentHashMap<String, Stack> expressions = new ConcurrentHashMap<>();
    private static final ExpressionCalculator calculator = new ExpressionCalculator();

    private ExpressionCalculator() {
    }

    public static Object calculate(String expression, Map<String, Object> params) {
        if (expression == null || expression.length() == 0) {
            logger.info("expression is null!");
            return null;
        }
        try {
            Stack<ExpressionNode> expNodeStack = calculator.parse(expression);
            return calculator.calculate(expNodeStack, params);
        } catch (Exception e) {
            logger.error("计算表达式异常", e);
            return null;
        }
    }

    public Stack<ExpressionNode> parse(String expression) {
        Stack<ExpressionNode> expNodeStack = expressions.get(expression);
        if (expNodeStack == null) {
            synchronized (ExpressionCalculator.class) {
                if (expNodeStack == null) {
                    ExpressionParser parser = new ExpressionParser(expression);
                    parser.parse();
                    expNodeStack = parser.getExpNodeStack();
                    expressions.put(expression, expNodeStack);
                }
            }
        }
        Stack<ExpressionNode> stackCopy = new Stack<>();
        stackCopy.addAll(expNodeStack);
        return stackCopy;
    }

    public Object calculate(Stack<ExpressionNode> expNodeStack, Map<String, Object> params) {
        Object result = null;
        while (!expNodeStack.isEmpty()) {
            ExpressionNode expNode = expNodeStack.pop();
            if (ExpressionNode.CONSTANT.equals(expNode.getNodeType())) {
                expNode.setValue(expNode.getExpression());
            } else if (ExpressionNode.VARIABLE.equals(expNode.getNodeType())) {
                Object value = getVariable(expNode, params);
                expNode.setValue(value);
            } else if (ExpressionNode.FUNCTION.equals(expNode.getNodeType())) {
                Object value = FunctionUtil.call(expNode.getExpression(), expNode.getArgumentValues());
                expNode.setValue(value);
            } else if (ExpressionNode.TEMPLATE.equals(expNode.getNodeType())) {
                String value = TemplateFormat.format(expNode.getExpression(), expNode.getArgumentValues());
                expNode.setValue(value);
            }
            result = expNode.getValue();
        }
        return result;
    }

    /** 仅支持boolean/string/integer类型 **/
    private Object convert(Integer varType, String value) {
        if (value == null) {
            return null;
        }
        try {
            if (Constant.BOOLEAN.equals(varType)) {
                return Boolean.parseBoolean(value);
            } else if (Constant.STRING.equals(varType)) {
                return value;
            } else if (Constant.INTEGER.equals(varType)) {
                return Integer.parseInt(value);
            } 
        } catch (Exception e) {
            logger.error("获取默认值异常", e);
        }
        return null;
    }

    private Object getVariable(ExpressionNode varExpNode, Map<String, Object> params) {
        Object value = JSONPath.eval(params, "$." + varExpNode.getExpression());
        if (value == null) {
            value = convert(varExpNode.getVarType(), varExpNode.getDefVal());
        }
        return value;
    }

    private static class TemplateFormat {

        public static String format(String pattern, Collection<?> collection) {
            return format(pattern, collection.toArray());
        }

        public static String format(String pattern, Object... objects) {
            if (objects == null || objects.length == 0) {
                return pattern;
            }
            StringBuilder temp = new StringBuilder(pattern);
            for (int i = 0; i < objects.length; i++) {
                String token = "{{" + i + "}}";
                int start = temp.indexOf(token);
                if (start == -1) {
                    break;
                }
                temp.replace(start, start + token.length(), String.valueOf(objects[i]));
            }
            return temp.toString();
        }
    }
    
}
View Code

 

 目前支持5中变量类型:

    /** 字符串 **/
    public static final Integer STRING = 1;
    /** 布尔值 **/
    public static final Integer BOOLEAN = 2;
    /** 整型 **/
    public static final Integer INTEGER = 3;
    /** 对象:map/pojo **/
    public static final Integer OBJECT = 4;
    /** 集合:array/list **/
    public static final Integer COLLECTION = 5;

 

变量引用:

#{var:type?def}
==============================================
var -> 变量名
type -> 类型
def -> 当变量为空(null)时的默认值

 

函数引用:

$fun(args...)
===========================
fun -> 函数名
args -> 参数列表

 

自定义函数Equals.java:

技术图片
public class Equals implements IFunction {

    @Override
    public String getName() {
        return "equals";
    }

    @Override
    public String getDesc() {
        return "判断两个表达式在字面上是否相同";
    }

    @Override
    public Object getDefVal() {
        return false;
    }

    @Override
    public Object process(Object... args) throws Exception {
        if (checkArgsIsEmpty(args)) {
            return false;
        }
        Object obj1 = args[0];
        Object obj2 = args[1];
        if (obj1 == null || obj2 == null) {
            return false;
        }
        if (obj1.getClass() == obj2.getClass()) {
            return obj1.equals(obj2);
        }
        return obj1.toString().equals(obj2.toString());
    }

}
View Code

 

写个例子测试下表达式计算:

    public static void main(String[] args) {
        Object user = new Object() {
            private String name = "lichmama";

            public String getName() {
                return name;
            }

            public void setName(String name) {
                this.name = name;
            }
        };
        String expression = "{‘name‘: ‘#{user.name}‘, ‘result‘: $equals(#{user.name}, lichmama})}";
        Map<String, Object> params = new HashMap<>();
        params.put("user", user);
        params.put("result", null);
        Object result = ExpressionCalculator.calculate(expression, params);
        System.out.println("表达式 => " + expression);
        System.out.println("入参 => " + JSON.toJSONString(params));
        System.out.println("结果 => " + result);
    }

 

输出如下:

表达式 => {‘name‘: ‘#{user.name}‘, ‘result‘: $equals(#{user.name}, lichmama})}
入参 => {"user":{"name":"lichmama"}}
结果 => {‘name‘: ‘lichmama‘, ‘result‘: true}

 

以上是关于实现一个支持自定义函数的模板表达式的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段1——vue主模板

VSCode自定义代码片段(vue主模板)

VSCode自定义代码片段2——.vue文件的模板

如何在片段着色器中进行自定义模板测试

pycharm自定义代码模板