这里的字符串分割使用的默认空格分隔,使用时需要特别注意模板格式。
废话不多说,直接上代码
package com.lunua.parse;
import com.alibaba.fastjson.JSONPath;
import com.google.common.collect.Lists;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Stack;
public class Parser {
enum Op{
GT(">"),GE(">="),LT("<"),LE("<="),EQ("=="),AND("and"),OR("or"),CONTAINS("contains"),CONTAINS_NOT("containsnot");
private String operator;
Op(String operator) {
this.operator = operator;
}
public String getOperator() {
return operator;
}
public static Op getByOperator(String operator){
if (operator == null || operator.length() == 0){
return null;
}
for (Op op: Op.values()) {
if (op.getOperator().equalsIgnoreCase(operator)){
return op;
}
}
return null;
}
}
private static final List<String> operator = Lists.newArrayList(Op.GT.getOperator(), Op.GE.getOperator(), Op.LT.getOperator(),Op.LE.getOperator(),Op.EQ.getOperator(),Op.AND.getOperator(),Op.OR.getOperator(),Op.CONTAINS.getOperator(),Op.CONTAINS_NOT.getOperator());
private static Map<String, Integer> operatorMap = null;
static {
operatorMap = new HashMap<String, Integer>();
operatorMap.put("(", 1);
operatorMap.put(")", 1);
operatorMap.put("and", 11);
operatorMap.put("or", 12);
operatorMap.put("==", 7);
operatorMap.put("!=", 7);
operatorMap.put(">", 6);
operatorMap.put(">=", 6);
operatorMap.put("<", 6);
operatorMap.put("<=", 6);
operatorMap.put("contains",14);
//operatorMap.put("containsNot",14);
operatorMap.put("containsnot",14);
}
private ArrayList<String> getTokens(String pattern){
String[] splitArr = pattern.split(" ");
ArrayList<String> tokens = new ArrayList<String>();
for (String split:splitArr) {
String trim = split.trim();
if (trim != null && trim.length() != 0){
tokens.add(trim);
}
}
return tokens;
}
/**
* 计算表达式
* @return
* @throws Exception
*/
public boolean calculate(String pattern,String log) throws Exception {
Stack<String> stack = new Stack<String>();
ArrayList<String> tokens = generateReversePolish(getTokens(pattern));
for (String token :tokens) {
//如果是操作数则入栈
if (!operatorMap.containsKey(token.toLowerCase())){
stack.push(token);
}
else{
String right = getValueFromContext(stack.pop(),log);
String left = getValueFromContext(stack.pop(), log);
stack.push(operate(left, right, token).toString());
}
}
if (stack.size() == 1){
String valueString = stack.pop();
if (valueString.equalsIgnoreCase("true")){
return true;
}
}
return false;
}
private String getValueFromContext(String variable, String log) {
if (!variable.contains("$")){
return variable;
}else {
if (variable.equalsIgnoreCase("$")){
return log;
}else if (variable.contains("$.")){
//使用jsonpath解析
return String.valueOf(JSONPath.read(log, variable));
}
}
return null;
}
private ArrayList<String> generateReversePolish(ArrayList<String> tokens) throws Exception{
//运算符stack, 和 操作数stack
Stack<String> operatorStack = new Stack<String>();
Stack<String> numberStack = new Stack<String>();
for (String token : tokens) {
//如果是操作数,直接插入到操作数stack
if (!operatorMap.containsKey(token.toLowerCase())){
numberStack.push(token);
}
else{
if (operatorStack.empty()){
operatorStack.push(token);
}
else{
//如果是右括号 则要找到左括号 并且一次入栈到 操作数栈
if (token.equals(")")){
String popToken = null;
try {
while( !(popToken = operatorStack.pop()).equals("(") ){
numberStack.push(popToken);
}
continue;
} catch (EmptyStackException e) {
throw new Exception("invalid expression: '", e);
}
}
String preOperator = operatorStack.peek();
//如果之前的操作符是( ,则不用比较优先级 当前操作符直接入栈
if (preOperator.equals("(")){
operatorStack.push(token);
}
//比较操作符优先级, 如果该操作符优先级大于等于 , 则直接入栈
else if (operatorMap.get(token) <= operatorMap.get(preOperator)){
operatorStack.push(token);
}
//如果该操作符优先级 小于, 则在将该操作符如栈之前 要把栈顶操作符 弹出 插入 操作数栈
else{
numberStack.push( operatorStack.pop());
operatorStack.push(token);
}
}
}
}
while (!operatorStack.empty()) {
numberStack.push( operatorStack.pop());
}
ArrayList<String> resArrayList = new ArrayList<String>();
String[] array = numberStack.toArray(new String[]{});
for (int i = 0; i < array.length; i++) {
resArrayList.add(array[i]);
}
return resArrayList;
}
private Boolean operate(String left,String right,String op){
Op operator = Op.getByOperator(op);
if (operator == null){
System.out.println("未知的操作 op = "+op);
return false;
}
Double leftNum =null;
Double rightNum = null;
switch (operator) {
case GT:
leftNum = Double.valueOf(left);
rightNum = Double.valueOf(right);
return leftNum>rightNum;
case GE:
leftNum = Double.valueOf(left);
rightNum = Double.valueOf(right);
return leftNum>=rightNum;
case LT:
leftNum = Double.valueOf(left);
rightNum = Double.valueOf(right);
return leftNum<rightNum;
case LE:
leftNum = Double.valueOf(left);
rightNum = Double.valueOf(right);
return leftNum<=rightNum;
case EQ:
return left.equals(right);
case AND:
if (!left.equals("true")){
return false;
}
if (!right.equals("true")){
return false;
}
return true;
case OR:
if (left.equals("true")){
return true;
}
if (right.equals("true")){
return true;
}
return false;
case CONTAINS:
if (left==null || right==null){
return false;
}
return left.contains(right);
case CONTAINS_NOT:
if (left==null || right==null){
return false;
}
return !left.contains(right);
default:return false;
}
}
public static void main(String[] args) throws Exception {
Parser parser = new Parser();
//System.out.println(parser.calculate("$ contains zhangsan","name=zhangsan"));
//System.out.println(parser.calculate("( $.name == zhangsan ) and ( $.age > 12 )","{\"name\":\"zhangsan\",\"age\":14}"));
System.out.println(parser.calculate("( $.name CONTAINSNOT zhangsan ) and ( $.age > 12 )","{\"name\":\"zhangsaan1\",\"age\":14}"));
System.out.println(parser.calculate("( $.name == zhangsan ) and ( $.age >= 12 )","{\"name\":\"zhangsan\",\"age\":12}"));
System.out.println(parser.calculate("( $.name == zhangsan ) and ( $.age >= 12 )","{\"name\":\"zhangsan\",\"age\":11}"));
}
}
参考:
https://juejin.im/post/5b9bb590e51d450e7579cf0d
https://gitee.com/wangzhuoa/ExpressionParser
pom依赖
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>29.0-jre</version>
</dependency>
<dependency>
<groupId>com.jayway.jsonpath</groupId>
<artifactId>json-path</artifactId>
<version>2.4.0</version>
</dependency>
这里json解析使用到了jsonpath
|