001 /** 002 * Copyright 2010 The Kuali Foundation Licensed under the 003 * Educational Community License, Version 2.0 (the "License"); you may 004 * not use this file except in compliance with the License. You may 005 * obtain a copy of the License at 006 * 007 * http://www.osedu.org/licenses/ECL-2.0 008 * 009 * Unless required by applicable law or agreed to in writing, 010 * software distributed under the License is distributed on an "AS IS" 011 * BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express 012 * or implied. See the License for the specific language governing 013 * permissions and limitations under the License. 014 */ 015 016 package org.kuali.student.common.messagebuilder.booleanmessage.ast; 017 018 import java.util.ArrayList; 019 import java.util.List; 020 import java.util.Locale; 021 import java.util.Map; 022 023 import org.antlr.runtime.ANTLRStringStream; 024 import org.antlr.runtime.CommonTokenStream; 025 import org.antlr.runtime.RecognitionException; 026 import org.antlr.runtime.Token; 027 import org.antlr.runtime.tree.CommonTreeAdaptor; 028 import org.antlr.runtime.tree.TreeAdaptor; 029 030 import org.kuali.student.common.messagebuilder.booleanmessage.BooleanMessage; 031 import org.kuali.student.common.messagebuilder.booleanmessage.ast.exceptions.BooleanFunctionException; 032 import org.kuali.student.common.messagebuilder.booleanmessage.ast.parsers.BooleanFunctionLexer; 033 import org.kuali.student.common.messagebuilder.booleanmessage.ast.parsers.BooleanFunctionParser; 034 035 import org.slf4j.Logger; 036 import org.slf4j.LoggerFactory; 037 038 public class BinaryMessageTree { 039 /** SLF4J logging framework */ 040 final static Logger logger = LoggerFactory.getLogger(BinaryMessageTree.class); 041 042 private BooleanNode root; 043 private ArrayList<BooleanNode> nodes; 044 private String language; 045 046 private Map<String, ? extends BooleanMessage> nodeMessageMap; 047 048 private static final TreeAdaptor adaptor = new CommonTreeAdaptor() { 049 public Object create(Token payload) { 050 return new BooleanNode(payload); 051 } 052 }; 053 054 /** 055 * Constructor. 056 */ 057 public BinaryMessageTree() { 058 language = Locale.getDefault().getLanguage(); 059 nodes = new ArrayList<BooleanNode>(); 060 } 061 062 /** 063 * Constructor. 064 * Creates a new binary message tree for the in the specified language 065 * for the boolean message map. 066 * 067 * @param messageLanguage Language for the message 068 * @param messageMap map of boolean messages 069 */ 070 public BinaryMessageTree(String language, Map<String, ? extends BooleanMessage> messageMap) { 071 this.language = language; 072 nodeMessageMap = messageMap; 073 nodes = new ArrayList<BooleanNode>(); 074 } 075 076 /** 077 * <p>Builds the binary message tree from the <code>booleanFunction</code>.</p> 078 * <p>Order of boolean operation: ANDs before ORs and 079 * operations inside parentheses before anything else.</p> 080 * <p>Example: A AND B OR C AND D evaluates to (A AND B) OR (C AND D).</p> 081 * 082 * @param booleanFunction: Boolean expression 083 * @return Root boolean node 084 */ 085 public BooleanNode buildTree(String booleanFunction){ 086 if (booleanFunction == null || booleanFunction.trim().isEmpty()) { 087 return null; 088 } 089 090 BooleanFunctionLexer lex = new BooleanFunctionLexer(new ANTLRStringStream(booleanFunction) ); 091 CommonTokenStream tokens = new CommonTokenStream(lex); 092 093 BooleanFunctionParser parser = new BooleanFunctionParser(tokens); 094 parser.setTreeAdaptor(adaptor); 095 096 // parse the expression 097 BooleanFunctionParser.booleanExpression_return booleanExpression = null; 098 try { 099 booleanExpression = parser.booleanExpression(); 100 } catch (RecognitionException e) { 101 String parserErrorMsg = parser.getErrorMessage(e, parser.getTokenNames()); 102 String errorMsg = "Boolean Function Parser Error. " + 103 "Invalid Boolean Expression: '" + booleanFunction + "'; " + parserErrorMsg; 104 throw new BooleanFunctionException(errorMsg, e); 105 } 106 // get the root of the AST from the parsed expression 107 return root = (BooleanNode) booleanExpression.getTree(); 108 } 109 110 /** 111 * This method walks the tree depth first, while setting node values with 112 * setNode(). Setting a nodes parent and adding the node to the ArrayList 113 * for later. This method has to be called after buildTree. 114 * 115 * @param bnode Boolean node 116 * @param parent Parent boolean node 117 */ 118 public void traverseTreePostOrder(BooleanNode bnode, BooleanNode parent) { 119 for (int i = 0; i < bnode.getChildCount(); i++) { 120 traverseTreePostOrder((BooleanNode) bnode.getChild(i), bnode); 121 } 122 setNode(bnode); 123 if (parent != null) { 124 bnode.setParent(parent); 125 } 126 127 if(logger.isDebugEnabled()) { 128 logger.debug(bnode.getText()); 129 } 130 nodes.add(bnode); 131 } 132 133 /** 134 * This method walks the tree depth first. Setting a nodes parent and 135 * adding the node to the ArrayList for later. 136 * This method has to be called after buildTree. 137 * 138 * @param bnode Boolean node 139 * @param parent Parent boolean node 140 */ 141 public void traverseTreePostOrderDontSetNode(BooleanNode bnode, BooleanNode parent) { 142 if (bnode != null) { 143 for (int i = 0; i < bnode.getChildCount(); i++ ) { 144 traverseTreePostOrderDontSetNode((BooleanNode)bnode.getChild(i), bnode); 145 } 146 147 if (parent != null) { 148 bnode.setParent(parent); 149 } 150 151 if(logger.isDebugEnabled()) { 152 logger.debug(bnode.getText()); 153 } 154 nodes.add(bnode); 155 } 156 } 157 158 /** 159 * Here we set the value(true or false) and the failure message for each node. 160 * Setting the failure message here does not mean the function failed, the node just holds the message. 161 * Failure is determined by the rules engine which will extract the failure message. 162 * 163 * @param bnode Boolean node 164 */ 165 private void setNode(BooleanNode bnode) { 166 bnode.setLanguage(language); 167 168 if (bnode.getChildCount() == 0) { 169 // If node is a leaf then set value and message 170 BooleanMessage message = nodeMessageMap.get(bnode.getLabel()); 171 bnode.setValue(message.isSuccesful()); 172 bnode.setNodeMessage(message.getMessage()); 173 } 174 else { 175 // If node is intermediate, compute value and set it 176 BooleanNode child0 = (BooleanNode)bnode.getChild(0); 177 BooleanNode child1 = (BooleanNode)bnode.getChild(1); 178 179 if ( bnode.getLabel().equalsIgnoreCase("+") ) { 180 // OR node 181 Boolean newValue = child0.getValue() || child1.getValue(); 182 bnode.setValue(newValue); 183 bnode.setNodeMessage("null"); 184 } 185 else if ( bnode.getLabel().equalsIgnoreCase("*") ) { 186 // AND node 187 Boolean newValue = child0.getValue() && child1.getValue(); 188 bnode.setValue(newValue); 189 bnode.setNodeMessage("null"); 190 } 191 } 192 } 193 194 /** 195 * Gets all boolean nodes. 196 * 197 * @return All boolean nodes 198 */ 199 public List<BooleanNode> getAllNodes() { 200 return nodes; 201 } 202 203 /** 204 * Gets the root boolean node. 205 * 206 * @return Root boolean node 207 */ 208 public BooleanNode getRoot() { 209 return root; 210 } 211 }