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
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
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
125 } else if (token.type == ExpressionToken.EndParenthesis) {
126 ExpressionToken operator = operatorStack.pop();
127
128
129 if (operator.type != ExpressionToken.StartParenthesis){
130
131
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();
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
358
359
360
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();
374 } else {
375 operatorStack.push(token);
376 }
377 }
378
379
380 while (operatorStack.isEmpty() == false){
381 rpnList.add(operatorStack.pop());
382 }
383
384 return rpnList;
385 }
386
387
388
389
390 private PropositionEditor ruleFromRPN(List<ExpressionToken> rpnList, List<PropositionEditor> rcs) {
391
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 }