View Javadoc

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.messagebuilder.booleanmessage.ast;
17  
18  import java.util.ArrayList;
19  import java.util.List;
20  import java.util.Locale;
21  import java.util.Map;
22  
23  import org.antlr.runtime.ANTLRStringStream;
24  import org.antlr.runtime.CommonTokenStream;
25  import org.antlr.runtime.RecognitionException;
26  import org.antlr.runtime.Token;
27  import org.antlr.runtime.tree.CommonTreeAdaptor;
28  import org.antlr.runtime.tree.TreeAdaptor;
29  
30  import org.kuali.student.common.messagebuilder.booleanmessage.BooleanMessage;
31  import org.kuali.student.common.messagebuilder.booleanmessage.ast.exceptions.BooleanFunctionException;
32  import org.kuali.student.common.messagebuilder.booleanmessage.ast.parsers.BooleanFunctionLexer;
33  import org.kuali.student.common.messagebuilder.booleanmessage.ast.parsers.BooleanFunctionParser;
34  
35  import org.slf4j.Logger;
36  import org.slf4j.LoggerFactory;
37  
38  public class BinaryMessageTree {
39      /** SLF4J logging framework */
40      final static Logger logger = LoggerFactory.getLogger(BinaryMessageTree.class);
41  
42      private BooleanNode root;
43  	private ArrayList<BooleanNode> nodes;
44  	private String language;
45  	
46  	private Map<String, ? extends BooleanMessage> nodeMessageMap;
47  	
48  	private static final TreeAdaptor adaptor = new CommonTreeAdaptor() {
49  		public Object create(Token payload) {
50  			return new BooleanNode(payload);
51  		}
52  	};
53  	
54  	/**
55  	 * Constructor.
56  	 */
57  	public BinaryMessageTree() {
58  		language = Locale.getDefault().getLanguage();
59  		nodes = new ArrayList<BooleanNode>();
60  	}
61  
62  	/**
63  	 * Constructor. 
64  	 * Creates a new binary message tree for the in the specified language
65  	 * for the boolean message map.
66  	 *  
67  	 * @param messageLanguage Language for the message 
68  	 * @param messageMap map of boolean messages
69  	 */
70  	public BinaryMessageTree(String language, Map<String, ? extends BooleanMessage> messageMap) {
71  		this.language = language;
72  		nodeMessageMap = messageMap;
73  		nodes = new ArrayList<BooleanNode>();
74  	}
75  	
76  	/**
77  	 * <p>Builds the binary message tree from the <code>booleanFunction</code>.</p>
78  	 * <p>Order of boolean operation: ANDs before ORs and 
79  	 * operations inside parentheses before anything else.</p>
80  	 * <p>Example: A AND B OR C AND D evaluates to (A AND B) OR (C AND D).</p>
81  	 * 
82  	 * @param booleanFunction: Boolean expression 
83  	 * @return Root boolean node
84  	 */
85  	public BooleanNode buildTree(String booleanFunction){
86  		if (booleanFunction == null || booleanFunction.trim().isEmpty()) {
87  			return null;
88  		}
89  		
90  		BooleanFunctionLexer lex = new BooleanFunctionLexer(new ANTLRStringStream(booleanFunction) );
91  		CommonTokenStream tokens = new CommonTokenStream(lex);
92  		
93  		BooleanFunctionParser parser = new BooleanFunctionParser(tokens);
94  		parser.setTreeAdaptor(adaptor);
95  		
96  		// parse the expression
97  		BooleanFunctionParser.booleanExpression_return booleanExpression = null;
98          try {
99          	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 }