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 }