重写逻辑表达式

最近碰到一个动态查询条件的问题,比如,前端界面上可选择输入姓名、性别、年龄、电话等查询条件,后端根据是否输入来动态构建查询sql where条件(即如果未输入则不作为查询条件)。如果这些条件最终是全and或全or起来则相对容易处理,但如果既有and又有or则动态构建就困难了。

这个问题经抽象可表达为动态重写逻辑表达式,即先写出完整的表达式,然后排除无效部分。例如,对于下面的完整逻辑表达式字符串:

(a) or (b) and (c)
  1. 如果 a 无效则重写为 (b) and (c) 
  2. 如果 b 无效则重写为 (a) or (c) 
  3. 如果 c 无效则重写为 (a) or (b)
  4. 如果 a 和 b 都无效则重写为 (c)  
  5. ...
  6. 如果全都无效则返回null

这个问题如何通用地解决呢?初看起来似乎很难,无从下手,但用ANTLR来解决则简单到出乎你的意料。

1、定义逻辑表达式语法

 1 # LogicExpr.g4文件
 2 
 3 grammar LogicExpr;
 4 
 5 stat: expr ;
 6 
 7 expr: expr AND expr             # and
 8     | expr OR expr              # or
 9     | '(' expr ')'              # group
10     | VAR                       # var
11     ;
12 
13 VAR : '(' KEY ')' ;
14 
15 AND: 'and' ;
16 OR: 'or' ;
17 KEY: [a-zA-Z0-9_]+ ;
18 WS: [ \t\r\n]+ -> skip ;
View Code

2、用ANTLR工具生成Java代码(略)

3、接着写排除算法

  1 package com.mycomp.antlr4.le;
  2 
  3 import java.util.Collection;
  4 
  5 import org.antlr.v4.runtime.ANTLRInputStream;
  6 import org.antlr.v4.runtime.CommonTokenStream;
  7 import org.antlr.v4.runtime.misc.Interval;
  8 
  9 import com.mycomp.antlr4.le.LogicExprParser.AndContext;      // 以下5个import是ANTLR生成的代码
 10 import com.mycomp.antlr4.le.LogicExprParser.GroupContext;
 11 import com.mycomp.antlr4.le.LogicExprParser.OrContext;
 12 import com.mycomp.antlr4.le.LogicExprParser.StatContext;
 13 import com.mycomp.antlr4.le.LogicExprParser.VarContext;
 14 
 15 public class LogicExprRewriter extends LogicExprBaseVisitor<String> {
 16 
 17     public static String rewrite(String le, Collection<String> invalidKeys) {
 18         ANTLRInputStream input = new ANTLRInputStream(le);
 19 
 20         LogicExprLexer lexer = new LogicExprLexer(input);
 21         CommonTokenStream tokens = new CommonTokenStream(lexer);
 22 
 23         LogicExprParser parser = new LogicExprParser(tokens);
 24         StatContext tree = parser.stat();
 25 
 26         LogicExprRewriter rewriter = new LogicExprRewriter(invalidKeys);
 27         return rewriter.visit(tree);
 28     }
 29 
 30     /** 无效的逻辑键集合 */
 31     private final Collection<String> invalidKeys;
 32 
 33     /**
 34      * 创建{@link LogicExprRewriter}对象。
 35      *
 36      * @param invalidKeys 无效的逻辑键集合。
 37      */
 38     private LogicExprRewriter(Collection<String> invalidKeys) {
 39         this.invalidKeys = invalidKeys;
 40     }
 41 
 42     @Override
 43     public String visitAnd(AndContext ctx) {
 44         String left = visit(ctx.expr(0));
 45         String right = visit(ctx.expr(1));
 46 
 47         String result;
 48         if (left == null) {
 49             result = (right == null) ? null : right;
 50         }
 51         else {
 52             if (right == null) {
 53                 result = left;
 54             }
 55             else {
 56                 Interval interval = Interval.of(ctx.expr(0).getStop().getStopIndex() + 1, ctx.expr(1).getStart().getStartIndex() - 1);
 57                 String and = ctx.getStart().getInputStream().getText(interval);
 58                 result = left + and + right;
 59             }
 60         }
 61 
 62         return result;
 63     }
 64 
 65     @Override
 66     public String visitOr(OrContext ctx) {
 67         String left = visit(ctx.expr(0));
 68         String right = visit(ctx.expr(1));
 69 
 70         String result;
 71         if (left == null) {
 72             result = (right == null) ? null : right;
 73         }
 74         else {
 75             if (right == null) {
 76                 result = left;
 77             }
 78             else {
 79                 Interval interval = Interval.of(ctx.expr(0).getStop().getStopIndex() + 1, ctx.expr(1).getStart().getStartIndex() - 1);
 80                 String and = ctx.getStart().getInputStream().getText(interval);
 81                 result = left + and + right;
 82             }
 83         }
 84 
 85         return result;
 86     }
 87 
 88     @Override
 89     public String visitVar(VarContext ctx) {
 90         // 去掉左右的括号。例如(a) => a
 91         String key = ctx.getText().substring(1, ctx.getText().length() - 1);
 92         boolean invalid = isInvalidKey(key);
 93         return invalid ? null : ctx.getText();
 94     }
 95 
 96     @Override
 97     public String visitGroup(GroupContext ctx) {
 98         String expr = visit(ctx.expr());
 99         return (expr == null) ? null : "(" + expr + ")";
100     }
101 
102     private boolean isInvalidKey(String key) {
103         return invalidKeys.contains(key);
104     }
105 
106 }
View Code

 4、最后看看单元测试

 1 package com.mycomp.antlr4.le;
 2 
 3 import static org.junit.Assert.assertEquals;
 4 import static org.junit.Assert.assertNull;
 5 
 6 import java.util.Arrays;
 7 import java.util.Collections;
 8 
 9 import org.junit.Test;
10 
11 public class LogicExprRewriterTest {
12 
13     @Test
14     public void 无无效键值时_应原样输出() {
15         String le = "(a) and (b) and ((c) or (d) and (e))";
16         String result = LogicExprRewriter.rewrite(le, Collections.emptyList());
17         assertEquals(le, result);
18     }
19 
20     @Test
21     public void 全部都为无效键值时_应输出null() {
22         String le = "(a) and (b) and ((c) or (d) and (e))";
23         String result = LogicExprRewriter.rewrite(le, Arrays.asList("a", "b", "c", "d", "e"));
24         assertNull(result);
25     }
26 
27     @Test
28     public void 保留空白字符() {
29         String le = "(a) \t\r\n and (b) or (c)";
30         String result = LogicExprRewriter.rewrite(le, Collections.emptyList());
31         assertEquals(le, result);
32     }
33 
34     @Test
35     public void 对于and操作_如果左边无效_则只返回右边() {
36         String le = "(a) and (b)";
37         String result = LogicExprRewriter.rewrite(le, Arrays.asList("b"));
38         assertEquals("(a)", result);
39     }
40 
41     @Test
42     public void 对于and操作_如果右边无效_则只返回左边() {
43         String le = "(a) and (b)";
44         String result = LogicExprRewriter.rewrite(le, Arrays.asList("a"));
45         assertEquals("(b)", result);
46     }
47 
48     @Test
49     public void 对于or操作_如果左边无效_则只返回右边() {
50         String le = "(a) or (b)";
51         String result = LogicExprRewriter.rewrite(le, Arrays.asList("b"));
52         assertEquals("(a)", result);
53     }
54 
55     @Test
56     public void 对于or操作_如果右边无效_则只返回左边() {
57         String le = "(a) or (b)";
58         String result = LogicExprRewriter.rewrite(le, Arrays.asList("a"));
59         assertEquals("(b)", result);
60     }
61 
62     @Test
63     public void 各类值组合测试() {
64         String le = "(a) and (b) and ((c) or (d) and (e))";
65         String result;
66 
67         result = LogicExprRewriter.rewrite(le, Arrays.asList("c", "e"));
68         assertEquals("(a) and (b) and ((d))", result);
69 
70         result = LogicExprRewriter.rewrite(le, Arrays.asList("a", "b", "e"));
71         assertEquals("((c) or (d))", result);
72     }
73 
74 }
View Code

就这么简单!

 

你可能感兴趣的:(重写逻辑表达式)