实现一个支持自定义函数的模板表达式
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 + "]"; } }
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; } }
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(); } } }
目前支持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()); } }
写个例子测试下表达式计算:
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}
以上是关于实现一个支持自定义函数的模板表达式的主要内容,如果未能解决你的问题,请参考以下文章