Coverage Report - org.kuali.student.core.statement.naturallanguage.translators.StatementParser
 
Classes in this File Line Coverage Branch Coverage Complexity
StatementParser
91%
81/89
79%
63/79
4.571
StatementParser$1
100%
1/1
N/A
4.571
 
 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.core.statement.naturallanguage.translators;
 17  
 
 18  
 import java.util.ArrayList;
 19  
 import java.util.HashMap;
 20  
 import java.util.Iterator;
 21  
 import java.util.List;
 22  
 import java.util.Map;
 23  
 
 24  
 import org.kuali.student.core.exceptions.OperationFailedException;
 25  
 import org.kuali.student.core.statement.dto.StatementOperatorTypeKey;
 26  
 import org.kuali.student.core.statement.entity.ReqComponent;
 27  
 import org.kuali.student.core.statement.entity.Statement;
 28  
 import org.kuali.student.core.statement.naturallanguage.util.ReqComponentReference;
 29  
 
 30  
 /**
 31  
  * This class parses a LU (learning unit) statement statement to generate
 32  
  * the boolean expression either as requirement components (e.g. R1 and R2)
 33  
  * or statements (e.g. S1 and S2).
 34  
  */
 35  
 public class StatementParser {
 36  
         private StringBuilder sb;
 37  
         private Map<String,String> idMap;
 38  
         private List<ReqComponentReference> reqComponentList;
 39  
         private String andOperator;
 40  
         private String orOperator;
 41  
         private int idCounter;
 42  
         private final static String STATEMENT_ID = "S";
 43  
         private final static String REC_COMPONENT_ID = "R";
 44  
 
 45  
         /**
 46  
          * Constructs a new statement parser with AND and OR statement
 47  
          * operator type keys.
 48  
          */
 49  
         public StatementParser() {
 50  7
                 this(StatementOperatorTypeKey.AND.name(), StatementOperatorTypeKey.OR.name());
 51  7
         }
 52  
 
 53  
         /**
 54  
          * Constructs a new statement parser.
 55  
          *
 56  
          * @param andOperator And operator
 57  
          * @param orOperator Or operator
 58  
          */
 59  37
         public StatementParser(final String andOperator, final String orOperator) {
 60  37
                 this.andOperator = andOperator.toLowerCase();
 61  37
                 this.orOperator = orOperator.toLowerCase();
 62  37
         }
 63  
 
 64  
         /**
 65  
          * Initialize boolean identifiers.
 66  
          */
 67  
         private void init() {
 68  49
                 idCounter = 1;
 69  49
                 this.idMap = new HashMap<String, String>();
 70  49
                 this.sb = new StringBuilder();
 71  49
         }
 72  
 
 73  
         /**
 74  
          * Generates a reduced boolean expression (unneeded brackets removed) of
 75  
          * the LU statement tree.
 76  
          * Traverses the LU statement and its children starting from the
 77  
          * <code>rootStatement</code> to generate the boolean expression.
 78  
          * No requirement components are included in this boolean expression.
 79  
          * E.g. Boolean expression: 'S1 AND (S2 OR S3)'
 80  
          *
 81  
          * @param rootStatement Starting (root) LU statement node
 82  
          * @return A boolean expression of the LU statement tree
 83  
          */
 84  
         public String getBooleanExpressionAsStatements(final Statement rootStatement) throws OperationFailedException {
 85  3
                 init();
 86  3
                 if(rootStatement.getChildren() == null || rootStatement.getChildren().isEmpty()) {
 87  0
                         return parseReqComponents(rootStatement, false);
 88  
                 } else {
 89  3
                         traverseStatementTreeAndReduce(rootStatement, false, null);
 90  
                 }
 91  3
                 return sb.toString();
 92  
         }
 93  
 
 94  
         /**
 95  
          * Generates a reduced boolean expression (unneeded brackets removed) of
 96  
          * the LU statement tree with leaf requirement components.
 97  
          * Traverses the LU statement and its children starting from the
 98  
          * <code>rootStatement</code> to generate the boolean expression.
 99  
          * No requirement components are included in this boolean expression.
 100  
          * E.g. Boolean expression: 'R1 AND (R2 OR (R3 AND R4))'
 101  
          *
 102  
          * @param rootStatement Starting (root) LU statement node
 103  
          * @return A boolean expression of the LU statement tree
 104  
          */
 105  
         public String getBooleanExpressionAsReqComponents(final Statement rootStatement) throws OperationFailedException {
 106  24
                 init();
 107  24
                 if(rootStatement.getChildren() == null || rootStatement.getChildren().isEmpty()) {
 108  17
                         return parseReqComponents(rootStatement, true);
 109  
                 } else {
 110  7
                         traverseStatementTreeAndReduce(rootStatement, true, null);
 111  
                 }
 112  7
                 return sb.toString();
 113  
         }
 114  
 
 115  
         /**
 116  
          * Gets boolean identifier map.
 117  
          *
 118  
          * @return Boolean identifier map
 119  
          */
 120  
         public Map<String, String> getIdMap() {
 121  0
                 return this.idMap;
 122  
         }
 123  
 
 124  
         /**
 125  
          * Gets a list of all leaf requirement components.
 126  
          *
 127  
          * @param rootStatement Starting (root) LU statement node
 128  
          * @return List of all requirement components
 129  
          */
 130  
         public List<ReqComponentReference> getLeafReqComponents(final Statement rootStatement) throws OperationFailedException {
 131  22
                 init();
 132  22
                 this.reqComponentList = new ArrayList<ReqComponentReference>();
 133  22
                 if(rootStatement.getChildren() == null || rootStatement.getChildren().isEmpty()) {
 134  16
                         this.reqComponentList.addAll(getReqComponents(rootStatement.getRequiredComponents()));
 135  
                 } else {
 136  6
                         traverseStatementTree(rootStatement);
 137  
                 }
 138  22
                 return this.reqComponentList;
 139  
         }
 140  
 
 141  
         /**
 142  
          * Traverses statement tree.
 143  
          *
 144  
          * @param rootStatement Root LU statement
 145  
          * @throws OperationFailedException
 146  
          */
 147  
         private void traverseStatementTree(Statement rootStatement) throws OperationFailedException {
 148  18
                 for(Iterator<Statement> it = rootStatement.getChildren().iterator(); it.hasNext();) {
 149  39
                         Statement stmt = it.next();
 150  39
                         if (stmt.getChildren() == null || stmt.getChildren().isEmpty()) {
 151  27
                                 this.reqComponentList.addAll(getReqComponents(stmt.getRequiredComponents()));
 152  
                         } else {
 153  12
                                 traverseStatementTree(stmt);
 154  
                         }
 155  39
                 }
 156  18
         }
 157  
 
 158  
         /**
 159  
          * Gets custom requirement components.
 160  
          *
 161  
          * @param list List of requirement components
 162  
          * @return List of requirement components
 163  
          * @throws OperationFailedException
 164  
          */
 165  
         private List<ReqComponentReference> getReqComponents(List<ReqComponent > list) throws OperationFailedException {
 166  43
                 List<ReqComponentReference> newList = new ArrayList<ReqComponentReference>(list.size());
 167  43
                 for(ReqComponent reqComp : list) {
 168  57
                         newList.add(new ReqComponentReference(reqComp, getReqComponentReferenceId(reqComp)));
 169  
                 }
 170  43
                 return newList;
 171  
         }
 172  
 
 173  
         /**
 174  
          * Traverses statement tree.
 175  
          *
 176  
          * @param rootStatement Root LU statement
 177  
          * @param parseReqComponent if true parses requirement component
 178  
          * @throws OperationFailedException
 179  
          */
 180  
         private void traverseStatementTreeAndReduce(Statement rootStatement, boolean parseReqComponent, Statement parent) throws OperationFailedException {
 181  30
                 if(rootStatement.getChildren() != null &&
 182  
                         (rootStatement.getOperator() == StatementOperatorTypeKey.OR ||
 183  
                         (parent != null && parent.getOperator() == StatementOperatorTypeKey.OR)) ) {
 184  14
                         this.sb.append("(");
 185  
                 }
 186  30
                 for(Iterator<Statement> it = rootStatement.getChildren().iterator(); it.hasNext();) {
 187  65
                         Statement stmt = it.next();
 188  65
                         if (stmt.getChildren() == null || stmt.getChildren().isEmpty()) {
 189  45
                                 if (parseReqComponent) {
 190  30
                                         this.sb.append(parseReqComponents(stmt, false));
 191  
                                 } else {
 192  
 //                                        this.sb.append(StatementUtil.formatId(stmt.getId()));
 193  15
                                         this.sb.append(getStatementReferenceId(stmt));
 194  
                                 }
 195  
                         } else {
 196  20
                                 traverseStatementTreeAndReduce(stmt, parseReqComponent, rootStatement);
 197  
                         }
 198  
 
 199  65
                         if (it.hasNext() && rootStatement != null && rootStatement.getOperator() != null) {
 200  35
                                 this.sb.append(" ");
 201  35
                                 this.sb.append(getOperator(rootStatement.getOperator()));
 202  35
                                 this.sb.append(" ");
 203  
                         }
 204  65
                 }
 205  30
                 if(rootStatement.getChildren() != null &&
 206  
                         (rootStatement.getOperator() == StatementOperatorTypeKey.OR ||
 207  
                         (parent != null && parent.getOperator() == StatementOperatorTypeKey.OR)) ) {
 208  14
                         this.sb.append(")");
 209  
                 }
 210  30
         }
 211  
 
 212  
         /**
 213  
          * Parses requirement components for LU statement.
 214  
          *
 215  
          * @param statement LU statement
 216  
          * @param reduce If true, reduces unneeded brackets
 217  
          * @return Parsed requirement components
 218  
          * @throws OperationFailedException
 219  
          */
 220  
         private String parseReqComponents(Statement statement, boolean reduce) throws OperationFailedException {
 221  47
                 if (statement.getRequiredComponents() == null) {
 222  0
                         return "";
 223  
                 }
 224  
 
 225  47
                 StringBuilder sb = new StringBuilder();
 226  47
                 if(!reduce && statement.getRequiredComponents().size() > 1)
 227  
                 {
 228  7
                         sb.append("(");
 229  
                 }
 230  47
                 for(Iterator<ReqComponent> it = statement.getRequiredComponents().iterator(); it.hasNext(); ) {
 231  62
                         ReqComponent reqComponent = it.next();
 232  62
                         sb.append(getReqComponentReferenceId(reqComponent));
 233  62
                         if (it.hasNext()) {
 234  15
                                 sb.append(" ");
 235  15
                                 sb.append(getOperator(statement.getOperator()));
 236  15
                                 sb.append(" ");
 237  
                         }
 238  62
                 }
 239  47
                 if(!reduce && statement.getRequiredComponents().size() > 1)
 240  
                 {
 241  7
                         sb.append(")");
 242  
                 }
 243  47
                 return sb.toString();
 244  
         }
 245  
 
 246  
         /**
 247  
          * Gets the statement operator type key translation.
 248  
          *
 249  
          * @param operator Statement operator type key
 250  
          * @return Operator translation
 251  
          * @throws OperationFailedException Invalid statement operator
 252  
          */
 253  
         private String getOperator(StatementOperatorTypeKey operator) throws OperationFailedException {
 254  1
                 switch(operator) {
 255  
                         case AND:
 256  25
                                 return this.andOperator;
 257  
                         case OR:
 258  25
                                 return this.orOperator;
 259  
                         default:
 260  0
                                 throw new OperationFailedException("Invalid statement operator: "+operator);
 261  
                 }
 262  
         }
 263  
 
 264  
         /**
 265  
          * Gets the statement reference identifier.
 266  
          *
 267  
          * @param statement LU statement
 268  
          * @return Statement reference identifier
 269  
          * @throws OperationFailedException Statement id is null
 270  
          */
 271  
         private String getStatementReferenceId(Statement statement) throws OperationFailedException {
 272  15
                 if(statement.getId() == null || statement.getId().isEmpty()) {
 273  0
                         throw new OperationFailedException("Statement id cannot be null");
 274  
                 }
 275  
 
 276  15
                 if(this.idMap.containsKey(statement.getId())) {
 277  0
                         return this.idMap.get(statement.getId());
 278  
                 }
 279  15
                 String id = STATEMENT_ID + this.idCounter++;
 280  15
                 this.idMap.put(statement.getId(), id);
 281  15
                 return id;
 282  
         }
 283  
 
 284  
         /**
 285  
          * Gets the requirement component reference identifier.
 286  
          *
 287  
          * @param reqComponent Requirement component
 288  
          * @return requirement component reference identifier
 289  
          * @throws OperationFailedException Requirement component id is null
 290  
          */
 291  
         private String getReqComponentReferenceId(ReqComponent reqComponent) throws OperationFailedException {
 292  119
                 if(reqComponent.getId() == null || reqComponent.getId().isEmpty()) {
 293  0
                         throw new OperationFailedException("Requirement component id cannot be null");
 294  
                 }
 295  
 
 296  119
                 if(this.idMap.containsKey(reqComponent.getId())) {
 297  0
                         return this.idMap.get(reqComponent.getId());
 298  
                 }
 299  119
                 String id = REC_COMPONENT_ID + this.idCounter++;
 300  119
                 this.idMap.put(reqComponent.getId(), id);
 301  119
                 return id;
 302  
         }
 303  
 }