View Javadoc

1   package org.kuali.rice.krms.util;
2   
3   import org.kuali.rice.krms.api.repository.LogicalOperator;
4   import org.kuali.rice.krms.dto.PropositionEditor;
5   import org.kuali.rice.krms.dto.RuleEditor;
6   
7   import java.util.ArrayList;
8   import java.util.List;
9   import java.util.Stack;
10  
11  public class RuleLogicExpressionParser {
12  
13      private String expression;
14  
15      private List<ExpressionToken> tokenList;
16  
17      private void checkExpressionSet() {
18          if (tokenList == null || expression == null) {
19              throw new java.lang.IllegalStateException("setExpression must be called first");
20          }
21      }
22  
23      public void checkMissingRCs(List<PropositionEditor> missingProps, List<PropositionEditor> props) {
24          checkExpressionSet();
25  
26          List<String> conditionValues = new ArrayList<String>();
27          for (ExpressionToken token : tokenList) {
28              if (token.type == ExpressionToken.Condition) {
29                  conditionValues.add(token.value);
30              }
31          }
32  
33          if (props != null && props.isEmpty()) {
34              for (PropositionEditor p : props) {
35                  boolean foundId = false;
36                  if (!conditionValues.isEmpty()) {
37                      for (String conditionValue : conditionValues) {
38                          if (conditionValue != null && conditionValue.equalsIgnoreCase(p.getCompoundOpCode())) {
39                              foundId = true;
40                              break;
41                          }
42                      }
43                  }
44                  if (!foundId) {
45                      missingProps.add(p);
46                  }
47              }
48          }
49      }
50  
51      public boolean validateExpression(List<String> errorMessages, List<String> propsAlpha) {
52          checkExpressionSet();
53          return doValidateExpression(errorMessages, tokenList, propsAlpha);
54      }
55  
56      private boolean doValidateExpression(List<String> errorMessages, final List<ExpressionToken> tokens, List<String> propsAlpha) {
57          boolean valid = true;
58          List<String> seenConditonValues = new ArrayList<String>();
59          // allow empty expression. ie. user wants the entire rule deleted.
60          if (tokens.size() == 0) {
61              return true;
62          }
63  
64          if ((tokens.get(0).type == ExpressionToken.StartParenthesis
65                  || tokens.get(0).type == ExpressionToken.Condition) == false) {
66              errorMessages.add("must start with ( or condition");
67              return false;
68          }
69          if ((tokenList.get(1).type == ExpressionToken.StartParenthesis) == false) {
70              errorMessages.add("parent compound proposition cannot be changed");
71              return false;
72          }
73          int lastIndex = tokens.size() - 1;
74          if ((tokens.get(lastIndex).type == ExpressionToken.EndParenthesis || tokens.get(lastIndex).type == ExpressionToken.Condition) == false) {
75              errorMessages.add("must end with ) or condition");
76              return false;
77          }
78          if (countToken(tokens, ExpressionToken.StartParenthesis) != countToken(tokens, ExpressionToken.EndParenthesis)) {
79              errorMessages.add("() not in pair");
80              return false;
81          }
82          if (!validateAndOr(errorMessages,tokens)){
83              return false;
84          }
85          // condition cannot duplicate
86          for (int i = 0; i < tokens.size(); i++) {
87              ExpressionToken token = tokens.get(i);
88              if (token.type == ExpressionToken.And) {
89                  if (!checkAnd(errorMessages, tokens, i)) {
90                      return false;
91                  }
92              } else if (token.type == ExpressionToken.Or) {
93                  if (!checkOr(errorMessages, tokens, i)) {
94                      return false;
95                  }
96              } else if (token.type == ExpressionToken.StartParenthesis) {
97                  if (!checkStartParenthesis(errorMessages, tokens, i)) {
98                      return false;
99                  }
100             } else if (token.type == ExpressionToken.EndParenthesis) {
101                 if (!checkEndParenthesis(errorMessages, tokens, i)) {
102                     return false;
103                 }
104             } else if (token.type == ExpressionToken.Condition) {
105                 if (seenConditonValues.contains(token.value)) {
106                     errorMessages.add("Condition " + token.value + " is duplicated.");
107                     return false;
108                 } else {
109                     seenConditonValues.add(token.value);
110                 }
111                 if (!checkCondition(errorMessages, tokens, i, propsAlpha)) {
112                     return false;
113                 }
114             }
115         }
116         return valid;
117     }
118 
119     private boolean validateAndOr(List<String> errorMessages, List<ExpressionToken> nodeList) {
120         Stack<ExpressionToken> operatorStack = new Stack<ExpressionToken>();
121 
122         for (ExpressionToken token : nodeList) {
123             if (token.type == ExpressionToken.Condition) {
124                 //do nothing
125             } else if (token.type == ExpressionToken.EndParenthesis) {
126                 ExpressionToken operator = operatorStack.pop();
127 
128                 //Check if first type is a OR or a AND
129                 if (operator.type != ExpressionToken.StartParenthesis){
130 
131                     //Check if all other types are the same as the first type.
132                     while (operatorStack.peek().type != ExpressionToken.StartParenthesis) {
133                         ExpressionToken next = operatorStack.pop();
134                         if (next.type != operator.type){
135                             errorMessages.add("Operators within parenthesis must be the same type.");
136                             return false;
137                         }
138                     }
139 
140                     operatorStack.pop();// pop the (
141                 }
142             } else {
143                 operatorStack.push(token);
144             }
145         }
146 
147         return true;
148     }
149 
150     private int countToken(List<ExpressionToken> tokenList, int type) {
151         int count = 0;
152         for (ExpressionToken t : tokenList) {
153             if (t.type == type) {
154                 count++;
155             }
156         }
157         return count;
158     }
159 
160     private boolean checkAnd(List<String> errorMessages, List<ExpressionToken> tokenList, int currentIndex) {
161         ExpressionToken prevToken = (tokenList == null || currentIndex - 1 < 0) ? null :
162                 tokenList.get(currentIndex - 1);
163         ExpressionToken nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size()) ? null :
164                 tokenList.get(currentIndex + 1);
165         boolean validToken = true;
166         if (prevToken != null && (prevToken.type == ExpressionToken.Condition || prevToken.type == ExpressionToken.EndParenthesis) == false) {
167             errorMessages.add("only ) and condition could sit before and");
168             validToken = false;
169         }
170         if (nextToken != null && (nextToken.type == ExpressionToken.Condition || nextToken.type == ExpressionToken.StartParenthesis) == false) {
171             errorMessages.add("only ( and condition could sit after and");
172             validToken = false;
173         }
174         return validToken;
175     }
176 
177     private boolean checkOr(List<String> errorMessages, List<ExpressionToken> tokenList, int currentIndex) {
178         ExpressionToken prevToken = (tokenList == null || currentIndex - 1 < 0) ? null :
179                 tokenList.get(currentIndex - 1);
180         ExpressionToken nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size()) ? null :
181                 tokenList.get(currentIndex + 1);
182         boolean validToken = true;
183         if (prevToken != null && (prevToken.type == ExpressionToken.Condition || prevToken.type == ExpressionToken.EndParenthesis) == false) {
184             errorMessages.add("only ) and condition could sit before or");
185             validToken = false;
186         }
187         if (nextToken != null && (nextToken.type == ExpressionToken.Condition || nextToken.type == ExpressionToken.StartParenthesis) == false) {
188             errorMessages.add("only ( and condition could sit after or");
189             validToken = false;
190         }
191         return validToken;
192     }
193 
194     private boolean checkStartParenthesis(List<String> errorMessages, List<ExpressionToken> tokenList, int currentIndex) {
195         ExpressionToken prevToken = (tokenList == null || currentIndex - 1 < 0) ? null :
196                 tokenList.get(currentIndex - 1);
197         ExpressionToken nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size()) ? null :
198                 tokenList.get(currentIndex + 1);
199         boolean validToken = true;
200         if (prevToken != null && (prevToken.type == ExpressionToken.Condition) == false) {
201             errorMessages.add("cannot alter compound proposition");
202             validToken = false;
203         }
204         if (nextToken != null && (nextToken.type == ExpressionToken.Condition || nextToken.type == ExpressionToken.StartParenthesis) == false) {
205             errorMessages.add("only ( and condition could sit after (");
206             validToken = false;
207         }
208         if (prevToken != null && (prevToken.type == ExpressionToken.Condition || nextToken.type == ExpressionToken.Condition || prevToken.type == ExpressionToken.StartParenthesis) == false) {
209             errorMessages.add("cannot alter compound proposition");
210             validToken = false;
211         }
212         return validToken;
213     }
214 
215     private boolean checkEndParenthesis(List<String> errorMessages, List<ExpressionToken> tokenList, int currentIndex) {
216         ExpressionToken prevToken = (tokenList == null || currentIndex - 1 < 0) ? null :
217                 tokenList.get(currentIndex - 1);
218         ExpressionToken nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size()) ? null :
219                 tokenList.get(currentIndex + 1);
220         boolean validToken = true;
221         if (prevToken != null && (prevToken.type == ExpressionToken.Condition || prevToken.type == ExpressionToken.EndParenthesis) == false) {
222             errorMessages.add("only condition and ) could sit before )");
223             validToken = false;
224         }
225         if (nextToken != null && (nextToken.type == ExpressionToken.Or || nextToken.type == ExpressionToken.And || nextToken.type == ExpressionToken.EndParenthesis) == false) {
226             errorMessages.add("only ), and, or could sit after )");
227             validToken = false;
228         }
229         return validToken;
230     }
231 
232     private boolean checkCondition(List<String> errorMessages, List<ExpressionToken> tokenList, int currentIndex,
233                                    List<String> propsAlpha) {
234         ExpressionToken prevToken = (tokenList == null || currentIndex - 1 < 0) ? null :
235                 tokenList.get(currentIndex - 1);
236         ExpressionToken nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size()) ? null :
237                 tokenList.get(currentIndex + 1);
238         boolean validToken = true;
239         if (prevToken != null && (prevToken.type == ExpressionToken.And || prevToken.type == ExpressionToken.Or || prevToken.type == ExpressionToken.StartParenthesis) == false) {
240             errorMessages.add("only and, or could sit before condition");
241             validToken = false;
242         }
243         if (nextToken != null && (nextToken.type == ExpressionToken.Or || nextToken.type == ExpressionToken.And || nextToken.type == ExpressionToken.EndParenthesis || nextToken.type == ExpressionToken.StartParenthesis) == false) {
244             errorMessages.add("only (, ), and, or could sit after condition");
245             validToken = false;
246         }
247         ExpressionToken conditionToken = tokenList.get(currentIndex);
248         String conditionLetter = conditionToken.value;
249         boolean validConditonLetter = false;
250         if (propsAlpha != null) {
251             for (String propAlpha : propsAlpha) {
252                 if (propAlpha != null &&
253                         propAlpha.equalsIgnoreCase(conditionLetter)) {
254                     validConditonLetter = true;
255                 }
256             }
257         }
258         if (!validConditonLetter) {
259             errorMessages.add(conditionLetter + " is not a valid conditon");
260             validToken = false;
261         }
262         return validToken;
263     }
264 
265     private List<ExpressionToken> getTokenList(List<String> tokenValueList) {
266         List<ExpressionToken> tokenList = new ArrayList<ExpressionToken>();
267         for (String value : tokenValueList) {
268             if (value.isEmpty()) {
269                 continue;
270             }
271             if ("(".equals(value)) {
272                 ExpressionToken t = new ExpressionToken();
273                 t.type = ExpressionToken.StartParenthesis;
274                 tokenList.add(t);
275             } else if (")".equals(value)) {
276                 ExpressionToken t = new ExpressionToken();
277                 t.type = ExpressionToken.EndParenthesis;
278                 tokenList.add(t);
279 
280             } else if ("and".equals(value)) {
281                 ExpressionToken t = new ExpressionToken();
282                 t.type = ExpressionToken.And;
283                 tokenList.add(t);
284 
285             } else if ("or".equals(value)) {
286                 ExpressionToken t = new ExpressionToken();
287                 t.type = ExpressionToken.Or;
288                 tokenList.add(t);
289 
290             } else {
291                 ExpressionToken t = new ExpressionToken();
292                 t.type = ExpressionToken.Condition;
293                 t.value = value;
294                 tokenList.add(t);
295 
296             }
297         }
298         return tokenList;
299     }
300 
301     private List<String> getTokenValue(String expression) {
302         expression = expression.toLowerCase();
303         List<String> tokenValueList = new ArrayList<String>();
304         StringBuffer tokenValue = new StringBuffer();
305         for (int i = 0; i < expression.length(); i++) {
306 
307             char ch = expression.charAt(i);
308             if (Character.isSpaceChar(ch)) {
309                 tokenValueList.add(tokenValue.toString());
310                 tokenValue = new StringBuffer();
311             } else if (ch == '(' || ch == ')') {
312                 tokenValueList.add(tokenValue.toString());
313                 tokenValue = new StringBuffer();
314                 tokenValueList.add(String.valueOf(ch));
315             } else {
316                 tokenValue.append(ch);
317             }
318         }
319         tokenValueList.add(tokenValue.toString());
320         return tokenValueList;
321     }
322 
323     public void setExpression(String expression) {
324         this.expression = expression;
325         List<String> tokenValueList = getTokenValue(expression);
326         this.tokenList = getTokenList(tokenValueList);
327     }
328 
329     public String getExpression() {
330         return expression;
331     }
332 
333     public List<ExpressionToken> getTokenList() {
334         return tokenList;
335     }
336 
337     public PropositionEditor parseExpressionIntoRule(RuleEditor ruleEditor) {
338 
339         PropositionEditor oldEditor = (PropositionEditor) ruleEditor.getProposition();
340         List<PropositionEditor> rcs = this.getPropositions(new ArrayList<PropositionEditor>(), oldEditor);
341 
342         List<ExpressionToken> rpnList = getRPN(tokenList);
343         return ruleFromRPN(rpnList, rcs);
344     }
345 
346     private List<PropositionEditor> getPropositions(List<PropositionEditor> propositions, PropositionEditor propositionEditor) {
347         propositions.add(propositionEditor);
348         if (propositionEditor.getCompoundComponents() != null) {
349             for (PropositionEditor child : propositionEditor.getCompoundEditors()) {
350                 this.getPropositions(propositions, child);
351             }
352         }
353         return propositions;
354     }
355 
356     /**
357      * If higher push to stack, else pop till less than or equal, add to list push to stack if ( push to stack if ) pop to
358      * list till (.
359      * <p/>
360      * http://en.wikipedia.org/wiki/Reverse_Polish_notation
361      */
362     private List<ExpressionToken> getRPN(List<ExpressionToken> nodeList) {
363         List<ExpressionToken> rpnList = new ArrayList<ExpressionToken>();
364         Stack<ExpressionToken> operatorStack = new Stack<ExpressionToken>();
365 
366         for (ExpressionToken token : nodeList) {
367             if (token.type == ExpressionToken.Condition) {
368                 rpnList.add(token);
369             } else if (token.type == ExpressionToken.EndParenthesis) {
370                 while (operatorStack.peek().type != ExpressionToken.StartParenthesis) {
371                     rpnList.add(operatorStack.pop());
372                 }
373                 operatorStack.pop();// pop the (
374             } else {
375                 operatorStack.push(token);
376             }
377         }
378 
379         //Add remaining operators to rpnlist
380         while (operatorStack.isEmpty() == false){
381             rpnList.add(operatorStack.pop());
382         }
383 
384         return rpnList;
385     }
386 
387     /**
388      * Build the binary tree from list of tokens
389      */
390     private PropositionEditor ruleFromRPN(List<ExpressionToken> rpnList, List<PropositionEditor> rcs) {
391         //if rule is empty
392         if (rpnList.size() == 0) {
393             return new PropositionEditor();
394         }
395 
396         Stack<PropositionEditor> conditionStack = new Stack<PropositionEditor>();
397         Stack<PropositionEditor> simpleProps = new Stack<PropositionEditor>();
398         for (ExpressionToken token : rpnList) {
399             if (token.type == ExpressionToken.Condition) {
400                 PropositionEditor rc = lookupPropositionEditor(rcs, token.value);
401                 if (rc.getPropositionTypeCode().equals("C")){
402                     rc.setCompoundEditors(new ArrayList<PropositionEditor>());
403                 }
404                 conditionStack.push(rc);
405             } else {
406                 if(simpleProps.empty()){
407                     simpleProps.push(conditionStack.pop());
408                 }
409                 simpleProps.push(conditionStack.pop());
410                 if(conditionStack.peek().getPropositionTypeCode().equals("C")) {
411                     PropositionEditor compound = conditionStack.pop();
412                     if (token.type == ExpressionToken.And){
413                         PropositionTreeUtil.setTypeForCompoundOpCode(compound, LogicalOperator.AND.getCode());
414                     } else if (token.type == ExpressionToken.Or) {
415                         PropositionTreeUtil.setTypeForCompoundOpCode(compound, LogicalOperator.OR.getCode());
416                     }
417                     while (!simpleProps.empty()) {
418                         compound.getCompoundEditors().add(simpleProps.pop());
419                     }
420                     conditionStack.push(compound);
421                 }
422             }
423         }
424 
425         return conditionStack.pop();
426     }
427 
428     private PropositionEditor lookupPropositionEditor(List<PropositionEditor> rcs, String key) {
429         if (rcs != null) {
430             for (PropositionEditor rc : rcs) {
431                 if (rc.getKey().equalsIgnoreCase(key)) {
432                     return rc;
433                 }
434             }
435         }
436         return null;
437     }
438 }