1 | |
|
2 | |
|
3 | |
|
4 | |
|
5 | |
|
6 | |
|
7 | |
|
8 | |
|
9 | |
|
10 | |
|
11 | |
|
12 | |
|
13 | |
|
14 | |
|
15 | |
|
16 | |
package org.kuali.student.core.statement.ui.client.widgets.rules; |
17 | |
|
18 | |
import java.util.ArrayList; |
19 | |
import java.util.List; |
20 | |
import java.util.Stack; |
21 | |
|
22 | |
import org.kuali.student.core.statement.dto.StatementInfo; |
23 | |
import org.kuali.student.core.statement.dto.StatementOperatorTypeKey; |
24 | |
import org.kuali.student.core.statement.ui.client.widgets.table.Node; |
25 | |
|
26 | |
import com.google.gwt.core.client.GWT; |
27 | |
|
28 | 0 | public class RuleExpressionParser { |
29 | |
|
30 | |
private String expression; |
31 | |
private List<Token> tokenList; |
32 | |
|
33 | |
private void checkExpressionSet() { |
34 | 0 | if (tokenList == null || expression == null) { |
35 | 0 | throw new java.lang.IllegalStateException("setExpression must be called first"); |
36 | |
} |
37 | 0 | } |
38 | |
|
39 | |
public void checkMissingRCs(List<ReqComponentVO> missingRCs, List<ReqComponentVO> rcs) { |
40 | 0 | checkExpressionSet(); |
41 | |
|
42 | 0 | List<String> conditionValues = new ArrayList<String>(); |
43 | 0 | for (Token token : tokenList) { |
44 | 0 | if (token.type == Token.Condition) { |
45 | 0 | conditionValues.add(token.value); |
46 | |
} |
47 | |
} |
48 | |
|
49 | 0 | if (rcs != null && !rcs.isEmpty()) { |
50 | 0 | for (ReqComponentVO rc : rcs) { |
51 | 0 | boolean foundId = false; |
52 | 0 | if (!conditionValues.isEmpty()) { |
53 | 0 | for (String conditionValue : conditionValues) { |
54 | 0 | if (conditionValue != null && conditionValue.equalsIgnoreCase(rc.getGuiReferenceLabelId())) { |
55 | 0 | foundId = true; |
56 | 0 | break; |
57 | |
} |
58 | |
} |
59 | |
} |
60 | 0 | if (!foundId) { |
61 | 0 | missingRCs.add(rc); |
62 | |
} |
63 | 0 | } |
64 | |
} |
65 | 0 | } |
66 | |
|
67 | |
public boolean validateExpression(List<String> errorMessages, List<ReqComponentVO> validRCs) { |
68 | 0 | checkExpressionSet(); |
69 | 0 | return doValidateExpression(errorMessages, tokenList, validRCs); |
70 | |
} |
71 | |
|
72 | |
private boolean doValidateExpression(List<String> errorMessages, final List<Token> tokens, List<ReqComponentVO> validRCs) { |
73 | 0 | boolean valid = true; |
74 | 0 | List<String> seenConditonValues = new ArrayList<String>(); |
75 | |
|
76 | 0 | if (tokens.size() == 0) { |
77 | 0 | return true; |
78 | |
} |
79 | |
|
80 | 0 | if ((tokens.get(0).type == Token.StartParenthesis |
81 | |
|| tokens.get(0).type == Token.Condition) == false) { |
82 | 0 | errorMessages.add("must start with ( or condition"); |
83 | 0 | return false; |
84 | |
} |
85 | 0 | int lastIndex = tokens.size() - 1; |
86 | 0 | if ((tokens.get(lastIndex).type == Token.EndParenthesis || tokens.get(lastIndex).type == Token.Condition) == false) { |
87 | 0 | errorMessages.add("must end with ) or condition"); |
88 | 0 | return false; |
89 | |
} |
90 | 0 | if (countToken(tokens, Token.StartParenthesis) != countToken(tokens, Token.EndParenthesis)) { |
91 | 0 | errorMessages.add("() not in pair"); |
92 | 0 | return false; |
93 | |
} |
94 | |
|
95 | 0 | for (int i = 0; i < tokens.size(); i++) { |
96 | 0 | Token token = tokens.get(i); |
97 | 0 | if (token.type == Token.And) { |
98 | 0 | if (!checkAnd(errorMessages, tokens, i)) { |
99 | 0 | return false; |
100 | |
} |
101 | 0 | } else if (token.type == Token.Or) { |
102 | 0 | if (!checkOr(errorMessages, tokens, i)) { |
103 | 0 | return false; |
104 | |
} |
105 | 0 | } else if (token.type == Token.StartParenthesis) { |
106 | 0 | if (!checkStartParenthesis(errorMessages, tokens, i)) { |
107 | 0 | return false; |
108 | |
} |
109 | 0 | } else if (token.type == Token.EndParenthesis) { |
110 | 0 | if (!checkEndParenthesis(errorMessages, tokens, i)) { |
111 | 0 | return false; |
112 | |
} |
113 | 0 | } else if (token.type == Token.Condition) { |
114 | 0 | if (seenConditonValues.contains(token.value)) { |
115 | 0 | errorMessages.add("Condition " + token.value + " is duplicated."); |
116 | 0 | return false; |
117 | |
} else { |
118 | 0 | seenConditonValues.add(token.value); |
119 | |
} |
120 | 0 | if (!checkCondition(errorMessages, tokens, i, validRCs)) { |
121 | 0 | return false; |
122 | |
} |
123 | |
} |
124 | |
} |
125 | 0 | return valid; |
126 | |
} |
127 | |
|
128 | |
private int countToken(List<Token> tokenList, int type) { |
129 | 0 | int count = 0; |
130 | 0 | for (Token t : tokenList) { |
131 | 0 | if (t.type == type) { |
132 | 0 | count++; |
133 | |
} |
134 | |
} |
135 | 0 | return count; |
136 | |
} |
137 | |
|
138 | |
private boolean checkAnd(List<String> errorMessages, List<Token> tokenList, int currentIndex) { |
139 | 0 | Token prevToken = (tokenList == null || currentIndex - 1 < 0)? null : |
140 | |
tokenList.get(currentIndex - 1); |
141 | 0 | Token nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size())? null : |
142 | |
tokenList.get(currentIndex + 1); |
143 | 0 | boolean validToken = true; |
144 | 0 | if (prevToken != null && (prevToken.type == Token.Condition || prevToken.type == Token.EndParenthesis) == false) { |
145 | 0 | errorMessages.add("only ) and condition could sit before and"); |
146 | 0 | validToken = false; |
147 | |
} |
148 | 0 | if (nextToken != null && (nextToken.type == Token.Condition || nextToken.type == Token.StartParenthesis) == false) { |
149 | 0 | errorMessages.add("only ( and condition could sit after and"); |
150 | 0 | validToken = false; |
151 | |
} |
152 | 0 | return validToken; |
153 | |
} |
154 | |
|
155 | |
private boolean checkOr(List<String> errorMessages, List<Token> tokenList, int currentIndex) { |
156 | 0 | Token prevToken = (tokenList == null || currentIndex - 1 < 0)? null : |
157 | |
tokenList.get(currentIndex - 1); |
158 | 0 | Token nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size())? null : |
159 | |
tokenList.get(currentIndex + 1); |
160 | 0 | boolean validToken = true; |
161 | 0 | if (prevToken != null && (prevToken.type == Token.Condition || prevToken.type == Token.EndParenthesis) == false) { |
162 | 0 | errorMessages.add("only ) and condition could sit before or"); |
163 | 0 | validToken = false; |
164 | |
} |
165 | 0 | if (nextToken != null && (nextToken.type == Token.Condition || nextToken.type == Token.StartParenthesis) == false) { |
166 | 0 | errorMessages.add("only ( and condition could sit after or"); |
167 | 0 | validToken = false; |
168 | |
} |
169 | 0 | return validToken; |
170 | |
} |
171 | |
|
172 | |
private boolean checkStartParenthesis(List<String> errorMessages, List<Token> tokenList, int currentIndex) { |
173 | 0 | Token prevToken = (tokenList == null || currentIndex - 1 < 0)? null : |
174 | |
tokenList.get(currentIndex - 1); |
175 | 0 | Token nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size())? null : |
176 | |
tokenList.get(currentIndex + 1); |
177 | 0 | boolean validToken = true; |
178 | 0 | if (prevToken != null && (prevToken.type == Token.And || prevToken.type == Token.Or || prevToken.type == Token.StartParenthesis) == false) { |
179 | 0 | errorMessages.add("only and, or, ( could sit before ("); |
180 | 0 | validToken = false; |
181 | |
} |
182 | 0 | if (nextToken != null && (nextToken.type == Token.Condition || nextToken.type == Token.StartParenthesis) == false) { |
183 | 0 | errorMessages.add("only ( and condition could sit after ("); |
184 | 0 | validToken = false; |
185 | |
} |
186 | 0 | return validToken; |
187 | |
} |
188 | |
|
189 | |
private boolean checkEndParenthesis(List<String> errorMessages, List<Token> tokenList, int currentIndex) { |
190 | 0 | Token prevToken = (tokenList == null || currentIndex - 1 < 0)? null : |
191 | |
tokenList.get(currentIndex - 1); |
192 | 0 | Token nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size())? null : |
193 | |
tokenList.get(currentIndex + 1); |
194 | 0 | boolean validToken = true; |
195 | 0 | if (prevToken != null && (prevToken.type == Token.Condition || prevToken.type == Token.EndParenthesis) == false) { |
196 | 0 | errorMessages.add("only condition and ) could sit before )"); |
197 | 0 | validToken = false; |
198 | |
} |
199 | 0 | if (nextToken != null && (nextToken.type == Token.Or || nextToken.type == Token.And || nextToken.type == Token.EndParenthesis) == false) { |
200 | 0 | errorMessages.add("only ), and, or could sit after )"); |
201 | 0 | validToken = false; |
202 | |
} |
203 | 0 | return validToken; |
204 | |
} |
205 | |
|
206 | |
private boolean checkCondition(List<String> errorMessages, List<Token> tokenList, int currentIndex, |
207 | |
List<ReqComponentVO> validRCs) { |
208 | 0 | Token prevToken = (tokenList == null || currentIndex - 1 < 0)? null : |
209 | |
tokenList.get(currentIndex - 1); |
210 | 0 | Token nextToken = (tokenList == null || currentIndex + 1 >= tokenList.size())? null : |
211 | |
tokenList.get(currentIndex + 1); |
212 | 0 | boolean validToken = true; |
213 | 0 | if (prevToken != null && (prevToken.type == Token.And || prevToken.type == Token.Or || prevToken.type == Token.StartParenthesis) == false) { |
214 | 0 | errorMessages.add("only and, or could sit before condition"); |
215 | 0 | validToken = false; |
216 | |
} |
217 | 0 | if (nextToken != null && (nextToken.type == Token.Or || nextToken.type == Token.And || nextToken.type == Token.EndParenthesis) == false) { |
218 | 0 | errorMessages.add("only ), and, or could sit after condition"); |
219 | 0 | validToken = false; |
220 | |
} |
221 | 0 | Token conditionToken = tokenList.get(currentIndex); |
222 | 0 | String conditionLetter = conditionToken.value; |
223 | 0 | boolean validConditonLetter = false; |
224 | 0 | if (validRCs != null) { |
225 | 0 | for (ReqComponentVO rc : validRCs) { |
226 | 0 | if (rc.getGuiReferenceLabelId() != null && |
227 | |
rc.getGuiReferenceLabelId().equalsIgnoreCase(conditionLetter)) { |
228 | 0 | validConditonLetter = true; |
229 | |
} |
230 | |
} |
231 | |
} |
232 | 0 | if (!validConditonLetter) { |
233 | 0 | errorMessages.add(conditionLetter + " is not a valid conditon"); |
234 | 0 | validToken = false; |
235 | |
} |
236 | 0 | return validToken; |
237 | |
} |
238 | |
|
239 | |
public Node parseExpressionIntoTree(String expression, StatementVO statementVO, String statementType) { |
240 | 0 | Node tree = null; |
241 | 0 | if (expression != null && expression.trim().length() > 0) { |
242 | 0 | StatementVO parsedS = parseExpressionIntoStatementVO(expression, statementVO, statementType); |
243 | 0 | if (parsedS != null) { |
244 | 0 | tree = parsedS.getTree(); |
245 | |
} |
246 | |
} |
247 | 0 | return tree; |
248 | |
} |
249 | |
|
250 | |
public StatementVO parseExpressionIntoStatementVO(String expression, StatementVO statementVO, String statementType) { |
251 | 0 | StatementVO parsedS = null; |
252 | |
|
253 | 0 | if (expression.trim().isEmpty()) { |
254 | 0 | statementVO.clearStatementAndReqComponents(); |
255 | 0 | return statementVO; |
256 | |
} |
257 | |
|
258 | 0 | List<ReqComponentVO> rcs = statementVO.getAllReqComponentVOs(); |
259 | |
try { |
260 | 0 | List<String> tokenValueList = getTokenValue(expression); |
261 | 0 | List<Token> tokenList = getTokenList(tokenValueList); |
262 | 0 | if (!doValidateExpression(new ArrayList<String>(), tokenList, rcs)) return null; |
263 | 0 | List<Node<Token>> nodeList = toNodeList(tokenList); |
264 | 0 | List<Node<Token>> rpnList = getRPN(nodeList); |
265 | 0 | parsedS = statementVOFromRPN(rpnList, rcs, statementType, statementVO); |
266 | |
|
267 | 0 | if (parsedS != null) { |
268 | 0 | parsedS.simplify(); |
269 | |
} |
270 | 0 | } catch (Exception error) { |
271 | 0 | GWT.log("Exception parsing",error); |
272 | 0 | } |
273 | 0 | return parsedS; |
274 | |
} |
275 | |
|
276 | |
private ReqComponentVO lookupReqComponent(List<ReqComponentVO> rcs, String guiKey) { |
277 | 0 | ReqComponentVO result = null; |
278 | 0 | if (rcs != null) { |
279 | 0 | for (ReqComponentVO rc : rcs) { |
280 | 0 | if (rc.getGuiReferenceLabelId() != null && rc.getGuiReferenceLabelId().equalsIgnoreCase(guiKey)) { |
281 | 0 | result = rc; |
282 | 0 | break; |
283 | |
} |
284 | |
} |
285 | |
} |
286 | 0 | return result; |
287 | |
} |
288 | |
|
289 | |
|
290 | |
private StatementVO statementVOFromRPN(List<Node<Token>> rpnList, List<ReqComponentVO> rcs, String statementType, StatementVO oldStatementVO) { |
291 | |
StatementVO statementVO; |
292 | |
|
293 | |
|
294 | 0 | if (rpnList.size() == 0) { |
295 | 0 | return new StatementVO(); |
296 | |
} |
297 | |
|
298 | 0 | Stack<Node<? extends Token>> conditionStack = new Stack<Node<? extends Token>>(); |
299 | 0 | for (Node<Token> node : rpnList) { |
300 | 0 | if (node.getUserObject().type == Token.Condition) { |
301 | 0 | ReqComponentVO rc = null; |
302 | 0 | Node<ReqComponentVO> rcNode = null; |
303 | 0 | rc = lookupReqComponent(rcs, node.getUserObject().value); |
304 | 0 | if (rc != null) { |
305 | 0 | rcNode = new Node<ReqComponentVO>(); |
306 | 0 | rcNode.setUserObject(rc); |
307 | |
} |
308 | 0 | conditionStack.push(rcNode); |
309 | 0 | } else if (node.getUserObject().type == Token.And || node.getUserObject().type == Token.Or) { |
310 | 0 | StatementVO subS = new StatementVO(); |
311 | 0 | subS.setTokenOperator(true); |
312 | 0 | StatementOperatorTypeKey op = null; |
313 | 0 | if (node.getUserObject().type == Token.And) { |
314 | 0 | op = StatementOperatorTypeKey.AND; |
315 | 0 | } else if (node.getUserObject().type == Token.Or) { |
316 | 0 | op = StatementOperatorTypeKey.OR; |
317 | |
} |
318 | 0 | StatementInfo statementInfo = new StatementInfo(); |
319 | 0 | statementInfo.setOperator(op); |
320 | 0 | statementInfo.setType(statementType); |
321 | 0 | statementInfo.setId(oldStatementVO.getStatementInfo().getId()); |
322 | 0 | subS.setStatementInfo(statementInfo); |
323 | 0 | Token right = conditionStack.pop().getUserObject(); |
324 | 0 | Token left = conditionStack.pop().getUserObject(); |
325 | 0 | List<ReqComponentVO> nodeRcs = new ArrayList<ReqComponentVO>(); |
326 | 0 | if (left instanceof ReqComponentVO) { |
327 | 0 | nodeRcs.add((ReqComponentVO) left); |
328 | |
} |
329 | 0 | if (right instanceof ReqComponentVO) { |
330 | 0 | nodeRcs.add((ReqComponentVO) right); |
331 | |
} |
332 | |
|
333 | |
|
334 | 0 | if (nodeRcs != null && nodeRcs.size() == 2) { |
335 | 0 | subS.addReqComponentVOs(nodeRcs); |
336 | |
} else { |
337 | 0 | if (left instanceof ReqComponentVO) { |
338 | 0 | subS.addStatementVO(wrapReqComponent(op, (ReqComponentVO)left, statementType)); |
339 | |
} else { |
340 | 0 | subS.addStatementVO((StatementVO)left); |
341 | |
} |
342 | 0 | if (right instanceof ReqComponentVO) { |
343 | 0 | subS.addStatementVO(wrapReqComponent(op, (ReqComponentVO)right, statementType)); |
344 | |
} else { |
345 | 0 | subS.addStatementVO((StatementVO)right); |
346 | |
} |
347 | |
} |
348 | |
|
349 | 0 | Node<StatementVO> nodeS = new Node<StatementVO>(); |
350 | 0 | nodeS.setUserObject(subS); |
351 | 0 | conditionStack.push(nodeS); |
352 | 0 | } |
353 | |
} |
354 | |
|
355 | 0 | if (conditionStack.peek().getUserObject() instanceof ReqComponentVO) { |
356 | 0 | statementVO = wrapReqComponent(StatementOperatorTypeKey.AND, |
357 | |
(ReqComponentVO)conditionStack.pop().getUserObject(), statementType); |
358 | |
} else { |
359 | 0 | statementVO = (StatementVO)conditionStack.pop().getUserObject(); |
360 | |
} |
361 | 0 | statementVO.getStatementInfo().setType(statementType); |
362 | |
|
363 | 0 | return statementVO; |
364 | |
} |
365 | |
|
366 | |
private StatementVO wrapReqComponent(StatementOperatorTypeKey op, ReqComponentVO rc, String statementType) { |
367 | 0 | StatementVO wrapS = new StatementVO(); |
368 | 0 | StatementInfo wrapStatementInfo = new StatementInfo(); |
369 | 0 | wrapStatementInfo.setOperator(op); |
370 | 0 | wrapStatementInfo.setType(statementType); |
371 | 0 | wrapS.setStatementInfo(wrapStatementInfo); |
372 | 0 | wrapS.addReqComponentVO(rc); |
373 | 0 | return wrapS; |
374 | |
} |
375 | |
|
376 | |
|
377 | |
|
378 | |
|
379 | |
|
380 | |
|
381 | |
|
382 | |
|
383 | |
private List<Node<Token>> getRPN(List<Node<Token>> nodeList) { |
384 | 0 | List<Node<Token>> rpnList = new ArrayList<Node<Token>>(); |
385 | 0 | Stack<Node<Token>> operatorStack = new Stack<Node<Token>>(); |
386 | |
|
387 | 0 | for (Node<Token> node : nodeList) { |
388 | 0 | if (node.getUserObject().type == Token.Condition) { |
389 | 0 | rpnList.add(node); |
390 | |
|
391 | 0 | } else if (node.getUserObject().type == Token.And) { |
392 | 0 | operatorStack.push(node); |
393 | 0 | } else if (node.getUserObject().type == Token.StartParenthesis) { |
394 | 0 | operatorStack.push(node); |
395 | 0 | } else if (node.getUserObject().type == Token.Or) { |
396 | |
|
397 | 0 | if (operatorStack.isEmpty() == false && operatorStack.peek().getUserObject().type == Token.And) { |
398 | |
do { |
399 | 0 | rpnList.add(operatorStack.pop()); |
400 | 0 | } while (operatorStack.isEmpty() == false && operatorStack.peek().getUserObject().type == Token.And); |
401 | |
} |
402 | |
|
403 | 0 | operatorStack.push(node); |
404 | 0 | } else if (node.getUserObject().type == Token.EndParenthesis) { |
405 | 0 | while (operatorStack.peek().getUserObject().type != Token.StartParenthesis) { |
406 | 0 | rpnList.add(operatorStack.pop()); |
407 | |
} |
408 | 0 | operatorStack.pop(); |
409 | |
} |
410 | |
} |
411 | 0 | if (operatorStack.isEmpty() == false) { |
412 | |
do { |
413 | 0 | rpnList.add(operatorStack.pop()); |
414 | 0 | } while (operatorStack.isEmpty() == false); |
415 | |
} |
416 | 0 | return rpnList; |
417 | |
} |
418 | |
|
419 | |
private List<Node<Token>> toNodeList(List<Token> tokenList) { |
420 | 0 | List<Node<Token>> nodeList = new ArrayList<Node<Token>>(); |
421 | 0 | for (Token token : tokenList) { |
422 | 0 | Node<Token> node = new Node<Token>(); |
423 | 0 | node.setUserObject(token); |
424 | 0 | nodeList.add(node); |
425 | 0 | } |
426 | 0 | return nodeList; |
427 | |
} |
428 | |
|
429 | |
private List<Token> getTokenList(List<String> tokenValueList) { |
430 | 0 | List<Token> tokenList = new ArrayList<Token>(); |
431 | 0 | for (String value : tokenValueList) { |
432 | 0 | if (value.isEmpty()) { |
433 | 0 | continue; |
434 | |
} |
435 | 0 | if ("(".equals(value)) { |
436 | 0 | Token t = new Token(); |
437 | 0 | t.type = Token.StartParenthesis; |
438 | 0 | tokenList.add(t); |
439 | 0 | } else if (")".equals(value)) { |
440 | 0 | Token t = new Token(); |
441 | 0 | t.type = Token.EndParenthesis; |
442 | 0 | tokenList.add(t); |
443 | |
|
444 | 0 | } else if ("and".equals(value)) { |
445 | 0 | Token t = new Token(); |
446 | 0 | t.type = Token.And; |
447 | 0 | tokenList.add(t); |
448 | |
|
449 | 0 | } else if ("or".equals(value)) { |
450 | 0 | Token t = new Token(); |
451 | 0 | t.type = Token.Or; |
452 | 0 | tokenList.add(t); |
453 | |
|
454 | 0 | } else { |
455 | 0 | Token t = new Token(); |
456 | 0 | t.type = Token.Condition; |
457 | 0 | t.value = value; |
458 | 0 | tokenList.add(t); |
459 | |
|
460 | 0 | } |
461 | |
} |
462 | 0 | return tokenList; |
463 | |
} |
464 | |
|
465 | |
private List<String> getTokenValue(String expression) { |
466 | 0 | expression = expression.toLowerCase(); |
467 | 0 | List<String> tokenValueList = new ArrayList<String>(); |
468 | 0 | StringBuffer tokenValue = new StringBuffer(); |
469 | 0 | for (int i = 0; i < expression.length(); i++) { |
470 | |
|
471 | 0 | char ch = expression.charAt(i); |
472 | 0 | if (ch == ' ') { |
473 | 0 | tokenValueList.add(tokenValue.toString()); |
474 | 0 | tokenValue = new StringBuffer(); |
475 | 0 | } else if (ch == '(' || ch == ')') { |
476 | 0 | tokenValueList.add(tokenValue.toString()); |
477 | 0 | tokenValue = new StringBuffer(); |
478 | 0 | tokenValueList.add(String.valueOf(ch)); |
479 | |
} else { |
480 | 0 | tokenValue.append(ch); |
481 | |
} |
482 | |
} |
483 | 0 | tokenValueList.add(tokenValue.toString()); |
484 | 0 | return tokenValueList; |
485 | |
} |
486 | |
|
487 | |
public void setExpression(String expression) { |
488 | 0 | this.expression = expression; |
489 | 0 | List<String> tokenValueList = getTokenValue(expression); |
490 | 0 | this.tokenList = getTokenList(tokenValueList); |
491 | 0 | } |
492 | |
|
493 | |
public String getExpression() { |
494 | 0 | return expression; |
495 | |
} |
496 | |
|
497 | |
} |