Coverage Report - org.kuali.student.core.statement.naturallanguage.translators.StatementParser
 
Classes in this File Line Coverage Branch Coverage Complexity
StatementParser
0%
0/89
0%
0/79
4.571
StatementParser$1
0%
0/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.common.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  0
                 this(StatementOperatorTypeKey.AND.name(), StatementOperatorTypeKey.OR.name());
 51  0
         }
 52  
 
 53  
         /**
 54  
          * Constructs a new statement parser.
 55  
          *
 56  
          * @param andOperator And operator
 57  
          * @param orOperator Or operator
 58  
          */
 59  0
         public StatementParser(final String andOperator, final String orOperator) {
 60  0
                 this.andOperator = andOperator.toLowerCase();
 61  0
                 this.orOperator = orOperator.toLowerCase();
 62  0
         }
 63  
 
 64  
         /**
 65  
          * Initialize boolean identifiers.
 66  
          */
 67  
         private void init() {
 68  0
                 idCounter = 1;
 69  0
                 this.idMap = new HashMap<String, String>();
 70  0
                 this.sb = new StringBuilder();
 71  0
         }
 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  0
                 init();
 86  0
                 if(rootStatement.getChildren() == null || rootStatement.getChildren().isEmpty()) {
 87  0
                         return parseReqComponents(rootStatement, false);
 88  
                 } else {
 89  0
                         traverseStatementTreeAndReduce(rootStatement, false, null);
 90  
                 }
 91  0
                 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  0
                 init();
 107  0
                 if(rootStatement.getChildren() == null || rootStatement.getChildren().isEmpty()) {
 108  0
                         return parseReqComponents(rootStatement, true);
 109  
                 } else {
 110  0
                         traverseStatementTreeAndReduce(rootStatement, true, null);
 111  
                 }
 112  0
                 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  0
                 init();
 132  0
                 this.reqComponentList = new ArrayList<ReqComponentReference>();
 133  0
                 if(rootStatement.getChildren() == null || rootStatement.getChildren().isEmpty()) {
 134  0
                         this.reqComponentList.addAll(getReqComponents(rootStatement.getRequiredComponents()));
 135  
                 } else {
 136  0
                         traverseStatementTree(rootStatement);
 137  
                 }
 138  0
                 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  0
                 for(Iterator<Statement> it = rootStatement.getChildren().iterator(); it.hasNext();) {
 149  0
                         Statement stmt = it.next();
 150  0
                         if (stmt.getChildren() == null || stmt.getChildren().isEmpty()) {
 151  0
                                 this.reqComponentList.addAll(getReqComponents(stmt.getRequiredComponents()));
 152  
                         } else {
 153  0
                                 traverseStatementTree(stmt);
 154  
                         }
 155  0
                 }
 156  0
         }
 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  0
                 List<ReqComponentReference> newList = new ArrayList<ReqComponentReference>(list.size());
 167  0
                 for(ReqComponent reqComp : list) {
 168  0
                         newList.add(new ReqComponentReference(reqComp, getReqComponentReferenceId(reqComp)));
 169  
                 }
 170  0
                 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  0
                 if(rootStatement.getChildren() != null &&
 182  
                         (rootStatement.getOperator() == StatementOperatorTypeKey.OR ||
 183  
                         (parent != null && parent.getOperator() == StatementOperatorTypeKey.OR)) ) {
 184  0
                         this.sb.append("(");
 185  
                 }
 186  0
                 for(Iterator<Statement> it = rootStatement.getChildren().iterator(); it.hasNext();) {
 187  0
                         Statement stmt = it.next();
 188  0
                         if (stmt.getChildren() == null || stmt.getChildren().isEmpty()) {
 189  0
                                 if (parseReqComponent) {
 190  0
                                         this.sb.append(parseReqComponents(stmt, false));
 191  
                                 } else {
 192  
 //                                        this.sb.append(StatementUtil.formatId(stmt.getId()));
 193  0
                                         this.sb.append(getStatementReferenceId(stmt));
 194  
                                 }
 195  
                         } else {
 196  0
                                 traverseStatementTreeAndReduce(stmt, parseReqComponent, rootStatement);
 197  
                         }
 198  
 
 199  0
                         if (it.hasNext() && rootStatement != null && rootStatement.getOperator() != null) {
 200  0
                                 this.sb.append(" ");
 201  0
                                 this.sb.append(getOperator(rootStatement.getOperator()));
 202  0
                                 this.sb.append(" ");
 203  
                         }
 204  0
                 }
 205  0
                 if(rootStatement.getChildren() != null &&
 206  
                         (rootStatement.getOperator() == StatementOperatorTypeKey.OR ||
 207  
                         (parent != null && parent.getOperator() == StatementOperatorTypeKey.OR)) ) {
 208  0
                         this.sb.append(")");
 209  
                 }
 210  0
         }
 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  0
                 if (statement.getRequiredComponents() == null) {
 222  0
                         return "";
 223  
                 }
 224  
 
 225  0
                 StringBuilder sb = new StringBuilder();
 226  0
                 if(!reduce && statement.getRequiredComponents().size() > 1)
 227  
                 {
 228  0
                         sb.append("(");
 229  
                 }
 230  0
                 for(Iterator<ReqComponent> it = statement.getRequiredComponents().iterator(); it.hasNext(); ) {
 231  0
                         ReqComponent reqComponent = it.next();
 232  0
                         sb.append(getReqComponentReferenceId(reqComponent));
 233  0
                         if (it.hasNext()) {
 234  0
                                 sb.append(" ");
 235  0
                                 sb.append(getOperator(statement.getOperator()));
 236  0
                                 sb.append(" ");
 237  
                         }
 238  0
                 }
 239  0
                 if(!reduce && statement.getRequiredComponents().size() > 1)
 240  
                 {
 241  0
                         sb.append(")");
 242  
                 }
 243  0
                 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  0
                 switch(operator) {
 255  
                         case AND:
 256  0
                                 return this.andOperator;
 257  
                         case OR:
 258  0
                                 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  0
                 if(statement.getId() == null || statement.getId().isEmpty()) {
 273  0
                         throw new OperationFailedException("Statement id cannot be null");
 274  
                 }
 275  
 
 276  0
                 if(this.idMap.containsKey(statement.getId())) {
 277  0
                         return this.idMap.get(statement.getId());
 278  
                 }
 279  0
                 String id = STATEMENT_ID + this.idCounter++;
 280  0
                 this.idMap.put(statement.getId(), id);
 281  0
                 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  0
                 if(reqComponent.getId() == null || reqComponent.getId().isEmpty()) {
 293  0
                         throw new OperationFailedException("Requirement component id cannot be null");
 294  
                 }
 295  
 
 296  0
                 if(this.idMap.containsKey(reqComponent.getId())) {
 297  0
                         return this.idMap.get(reqComponent.getId());
 298  
                 }
 299  0
                 String id = REC_COMPONENT_ID + this.idCounter++;
 300  0
                 this.idMap.put(reqComponent.getId(), id);
 301  0
                 return id;
 302  
         }
 303  
 }