Coverage Report - org.kuali.student.common.ui.client.widgets.rules.RuleExpressionParser
 
Classes in this File Line Coverage Branch Coverage Complexity
RuleExpressionParser
0%
0/285
0%
0/304
8.381
 
 1  
 /**
 2  
  * Copyright 2010 The Kuali Foundation Licensed under the
 3  
  * Educational Community License, Version 2.0 (the "License"); you may
 4  
  * not use this file except in compliance with the License. You may
 5  
  * obtain a copy of the License at
 6  
  *
 7  
  * http://www.osedu.org/licenses/ECL-2.0
 8  
  *
 9  
  * Unless required by applicable law or agreed to in writing,
 10  
  * software distributed under the License is distributed on an "AS IS"
 11  
  * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
 12  
  * or implied. See the License for the specific language governing
 13  
  * permissions and limitations under the License.
 14  
  */
 15  
 
 16  
 package org.kuali.student.common.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.common.ui.client.widgets.table.Node;
 23  
 import org.kuali.student.core.statement.dto.StatementInfo;
 24  
 import org.kuali.student.core.statement.dto.StatementOperatorTypeKey;
 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  
         // allow empty expression. ie. user wants the entire rule deleted.
 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  
         // condition cannot duplicate
 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);
 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  
     /** Build the binary tree from list of tokens*/
 290  
     private StatementVO statementVOFromRPN(List<Node<Token>> rpnList, List<ReqComponentVO> rcs, String statementType) {
 291  
         StatementVO statementVO;        
 292  
 
 293  
         //if rule is empty
 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
                 subS.setStatementInfo(statementInfo);
 322  0
                 Token right = conditionStack.pop().getUserObject();
 323  0
                 Token left = conditionStack.pop().getUserObject();
 324  0
                 List<ReqComponentVO> nodeRcs = new ArrayList<ReqComponentVO>();
 325  0
                 if (left instanceof ReqComponentVO) {
 326  0
                     nodeRcs.add((ReqComponentVO) left);
 327  
                 }
 328  0
                 if (right instanceof ReqComponentVO) {
 329  0
                     nodeRcs.add((ReqComponentVO) right);
 330  
                 }
 331  
                 // if both left and right nodes are rcs add them into the list of rcs in subS
 332  
                 // otherwise wrap the one that is a RC and add the left and right tokens as statementVO into subS
 333  0
                 if (nodeRcs != null && nodeRcs.size() == 2) {
 334  0
                     subS.addReqComponentVOs(nodeRcs);
 335  
                 } else {
 336  0
                     if (left instanceof ReqComponentVO) {
 337  0
                         subS.addStatementVO(wrapReqComponent(op, (ReqComponentVO)left, statementType));
 338  
                     } else {
 339  0
                         subS.addStatementVO((StatementVO)left);
 340  
                     }
 341  0
                     if (right instanceof ReqComponentVO) {
 342  0
                         subS.addStatementVO(wrapReqComponent(op, (ReqComponentVO)right, statementType));
 343  
                     } else {
 344  0
                         subS.addStatementVO((StatementVO)right);
 345  
                     }
 346  
                 }
 347  
                 
 348  0
                 Node<StatementVO> nodeS = new Node<StatementVO>();
 349  0
                 nodeS.setUserObject(subS);
 350  0
                 conditionStack.push(nodeS);
 351  0
             }
 352  
         }
 353  
         
 354  0
         if (conditionStack.peek().getUserObject() instanceof ReqComponentVO) {
 355  0
             statementVO = wrapReqComponent(StatementOperatorTypeKey.AND,
 356  
                     (ReqComponentVO)conditionStack.pop().getUserObject(), statementType);
 357  
         } else {
 358  0
             statementVO = (StatementVO)conditionStack.pop().getUserObject();
 359  
         }
 360  0
         statementVO.getStatementInfo().setType(statementType);
 361  
 
 362  0
         return statementVO;
 363  
     }
 364  
     
 365  
     private StatementVO wrapReqComponent(StatementOperatorTypeKey op, ReqComponentVO rc, String statementType) {
 366  0
         StatementVO wrapS = new StatementVO();
 367  0
         StatementInfo wrapStatementInfo = new StatementInfo();
 368  0
         wrapStatementInfo.setOperator(op);
 369  0
         wrapStatementInfo.setType(statementType);
 370  0
         wrapS.setStatementInfo(wrapStatementInfo);
 371  0
         wrapS.addReqComponentVO(rc);
 372  0
         return wrapS;
 373  
     }
 374  
     
 375  
     /**
 376  
      * 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
 377  
      * list till (.
 378  
      * 
 379  
      * http://en.wikipedia.org/wiki/Reverse_Polish_notation
 380  
      * 
 381  
      */
 382  
     private List<Node<Token>> getRPN(List<Node<Token>> nodeList) {
 383  0
         List<Node<Token>> rpnList = new ArrayList<Node<Token>>();
 384  0
         Stack<Node<Token>> operatorStack = new Stack<Node<Token>>();
 385  
 
 386  0
         for (Node<Token> node : nodeList) {
 387  0
             if (node.getUserObject().type == Token.Condition) {
 388  0
                 rpnList.add(node);
 389  
 
 390  0
             } else if (node.getUserObject().type == Token.And) {
 391  0
                 operatorStack.push(node);
 392  0
             } else if (node.getUserObject().type == Token.StartParenthesis) {
 393  0
                 operatorStack.push(node);
 394  0
             } else if (node.getUserObject().type == Token.Or) {
 395  
 
 396  0
                 if (operatorStack.isEmpty() == false && operatorStack.peek().getUserObject().type == Token.And) {
 397  
                     do {
 398  0
                         rpnList.add(operatorStack.pop());
 399  0
                     } while (operatorStack.isEmpty() == false && operatorStack.peek().getUserObject().type == Token.And);
 400  
                 }
 401  
 
 402  0
                 operatorStack.push(node);
 403  0
             } else if (node.getUserObject().type == Token.EndParenthesis) {
 404  0
                 while (operatorStack.peek().getUserObject().type != Token.StartParenthesis) {
 405  0
                     rpnList.add(operatorStack.pop());
 406  
                 }
 407  0
                 operatorStack.pop();// pop the (
 408  
             }
 409  
         }
 410  0
         if (operatorStack.isEmpty() == false) {
 411  
             do {
 412  0
                 rpnList.add(operatorStack.pop());
 413  0
             } while (operatorStack.isEmpty() == false);
 414  
         }
 415  0
         return rpnList;
 416  
     }
 417  
     
 418  
     private List<Node<Token>> toNodeList(List<Token> tokenList) {
 419  0
         List<Node<Token>> nodeList = new ArrayList<Node<Token>>();
 420  0
         for (Token token : tokenList) {
 421  0
             Node<Token> node = new Node<Token>();
 422  0
             node.setUserObject(token);
 423  0
             nodeList.add(node);
 424  0
         }
 425  0
         return nodeList;
 426  
     }
 427  
     
 428  
     private List<Token> getTokenList(List<String> tokenValueList) {
 429  0
         List<Token> tokenList = new ArrayList<Token>();
 430  0
         for (String value : tokenValueList) {
 431  0
             if (value.isEmpty()) {
 432  0
                 continue;
 433  
             }
 434  0
             if ("(".equals(value)) {
 435  0
                 Token t = new Token();
 436  0
                 t.type = Token.StartParenthesis;
 437  0
                 tokenList.add(t);
 438  0
             } else if (")".equals(value)) {
 439  0
                 Token t = new Token();
 440  0
                 t.type = Token.EndParenthesis;
 441  0
                 tokenList.add(t);
 442  
 
 443  0
             } else if ("and".equals(value)) {
 444  0
                 Token t = new Token();
 445  0
                 t.type = Token.And;
 446  0
                 tokenList.add(t);
 447  
 
 448  0
             } else if ("or".equals(value)) {
 449  0
                 Token t = new Token();
 450  0
                 t.type = Token.Or;
 451  0
                 tokenList.add(t);
 452  
 
 453  0
             } else {
 454  0
                 Token t = new Token();
 455  0
                 t.type = Token.Condition;
 456  0
                 t.value = value;
 457  0
                 tokenList.add(t);
 458  
 
 459  0
             }
 460  
         }
 461  0
         return tokenList;
 462  
     }
 463  
     
 464  
     private List<String> getTokenValue(String expression) {
 465  0
         expression = expression.toLowerCase();
 466  0
         List<String> tokenValueList = new ArrayList<String>();
 467  0
         StringBuffer tokenValue = new StringBuffer();
 468  0
         for (int i = 0; i < expression.length(); i++) {
 469  
 
 470  0
             char ch = expression.charAt(i);
 471  0
             if (ch == ' ') {
 472  0
                 tokenValueList.add(tokenValue.toString());
 473  0
                 tokenValue = new StringBuffer();
 474  0
             } else if (ch == '(' || ch == ')') {
 475  0
                 tokenValueList.add(tokenValue.toString());
 476  0
                 tokenValue = new StringBuffer();
 477  0
                 tokenValueList.add(String.valueOf(ch));
 478  
             } else {
 479  0
                 tokenValue.append(ch);
 480  
             }
 481  
         }
 482  0
         tokenValueList.add(tokenValue.toString());
 483  0
         return tokenValueList;
 484  
     }
 485  
 
 486  
     public void setExpression(String expression) {
 487  0
         this.expression = expression;
 488  0
         List<String> tokenValueList = getTokenValue(expression);
 489  0
         this.tokenList = getTokenList(tokenValueList);
 490  0
     }
 491  
 
 492  
     public String getExpression() {
 493  0
         return expression;
 494  
     }    
 495  
         
 496  
 }