重寫邏輯表達式

最近碰到一個動態查詢條件的問題,好比,前端界面上可選擇輸入姓名、性別、年齡、電話等查詢條件,後端根據是否輸入來動態構建查詢sql where條件(即若是未輸入則不做爲查詢條件)。若是這些條件最終是全and或全or起來則相對容易處理,但若是既有and又有or則動態構建就困難了。前端

這個問題經抽象可表達爲動態重寫邏輯表達式,即先寫出完整的表達式,而後排除無效部分。例如,對於下面的完整邏輯表達式字符串:java

(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來解決則簡單到出乎你的意料。算法

一、定義邏輯表達式語法sql

 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

二、用ANTLR工具生成Java代碼(略)後端

三、接着寫排除算法ide

  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

 四、最後看看單元測試工具

 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

就這麼簡單!單元測試

相關文章
相關標籤/搜索