initial load
[DTRules.git] / DTRules / src / main / java / com / dtrules / decisiontables / RDecisionTable.java
blob07aa449f7e439676195905061951b3c438f763bf
1 /*
2 * $Id$
3 *
4 * Copyright 2004-2007 MTBJ, Inc.
5 *
6 * Licensed under the Apache License, Version 2.0 (the "License");
7 * you may not use this file except in compliance with the License.
8 * You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
17 */
19 package com.dtrules.decisiontables;
21 import java.io.PrintStream;
22 import java.util.ArrayList;
23 import java.util.HashMap;
24 import java.util.Iterator;
25 import java.util.List;
26 import java.util.Map;
28 import com.dtrules.decisiontables.DTNode.Coordinate;
29 import com.dtrules.infrastructure.RulesException;
30 import com.dtrules.interpreter.ARObject;
31 import com.dtrules.interpreter.IRObject;
32 import com.dtrules.interpreter.RArray;
33 import com.dtrules.interpreter.RName;
34 import com.dtrules.interpreter.RNull;
35 import com.dtrules.interpreter.RString;
36 import com.dtrules.session.DTState;
37 import com.dtrules.session.EntityFactory;
38 import com.dtrules.session.ICompilerError;
39 import com.dtrules.session.IRSession;
40 import com.dtrules.session.RuleSet;
41 import com.dtrules.xmlparser.GenericXMLParser;
43 /**
44 * Decision Tables are the classes that hold the Rules for a set of Policy
45 * implemented using DTRules. There are three types: <br><br>
47 * BALANCED -- These decision tables expect all branches to be defined in the condition table <br>
48 * ALL -- Evaluates all the columns, then executes all the actions, in the order they
49 * are specified, for all columns whose conditions are met.<br>
50 * FIRST -- Effectively evaluates each column, and executes only the first Column whose
51 * conditions are met.<br>
52 * @author paul snow
53 * Mar 1, 2007
56 public class RDecisionTable extends ARObject {
58 private final RName dtname; // The decision table's name.
60 private String filename = null; // Filename of Excel file where the table is defined,
61 // if this decision table is defined in Excel.
63 enum UnbalancedType { FIRST, ALL }; // Unbalanced Table Types.
64 public static enum Type {
65 BALANCED { void build(RDecisionTable dt) {dt.buildBalanced(); }},
66 FIRST { void build(RDecisionTable dt) {dt.buildUnbalanced(UnbalancedType.FIRST); }},
67 ALL { void build(RDecisionTable dt) {dt.buildUnbalanced(UnbalancedType.ALL); }};
68 abstract void build(RDecisionTable dt);
71 public Type type = Type.BALANCED; // By default, all decision tables are balanced.
73 public static final int MAXCOL = 16; // The Maximum number of columns in a decision table.
75 int maxcol = 1; // The number of columns in this decision table.
77 private final RuleSet ruleset; // A decision table must belong to a particular ruleset
79 public final Map<RName,String> fields = new HashMap<RName, String>(); // Holds meta information about this decision table.
81 private boolean compiled=false; // The decision table isn't compiled
82 // until fully constructed. And
83 // it won't be if compilation fails.
84 String [][] conditiontable;
85 String [] conditions; // The conditions in formal. The formal langauge is compiled to get the postfix
86 String [] conditionsPostfix; // The conditions in postfix. derived from the formal
87 String [] conditionsComment; // A comment per condition.
88 IRObject [] rconditions; // Each compiled condition
90 String [][] actiontable;
91 String [] actions;
92 String [] actionsComment;
93 String [] actionsPostfix;
94 IRObject [] ractions; // Each compiled action
97 String [] initialActions; // A list of actions to be executed each time the
98 // decision table is executed before the conditions
99 // are evaluated.
100 IRObject [] rinitialActions;
101 String [] initialActionsPostfix; // Compiled Initial Actions
102 String [] initialActionsComment; // Comment for Initial Actions
103 String [] contexts; // Contexts in which to execute this table.
104 String [] contextsPostfix; // The Postfix for each context statement.
105 String contextsrc; // For Tracing...
106 IRObject rcontext; // lists of entities. It is best if this is done within the table than
107 // by the calling table.
109 List<ICompilerError> errorlist = new ArrayList<ICompilerError>();
110 DTNode decisiontree=null;
112 private int numberOfRealColumns = 0; // Number of real columns (as unbalanced tables can have
113 // far more columns than they appear to have).
115 public int getNumberOfRealColumns() {
116 if(decisiontree==null)return 0;
117 return decisiontree.countColumns();
121 * Check for errors in the decision table. Returns the column
122 * and row of a problem if one is found. If nothing is wrong,
123 * a null is returned.
124 * @return
126 public Coordinate validate(){
127 if(decisiontree==null){
128 if(actions !=null && actions.length==0)return null;
129 return new Coordinate(0,0);
131 return decisiontree.validate();
134 BalanceTable balanceTable = null; // Helper class to build a balanced or optimized version
135 // of this decision table.
137 public boolean isCompiled(){return compiled;}
141 public String getFilename() {
142 return filename;
145 public void setFilename(String filename) {
146 this.filename = filename;
149 @Override
150 public IRObject clone(IRSession s) throws RulesException {
151 RDecisionTable dt = new RDecisionTable(s,ruleset, dtname.stringValue());
152 dt.numberOfRealColumns = numberOfRealColumns;
153 dt.conditiontable = conditiontable.clone();
154 dt.conditions = conditions.clone();
155 dt.conditionsPostfix = conditionsPostfix.clone();
156 dt.conditionsComment = conditionsComment.clone();
157 dt.rconditions = rconditions.clone();
158 dt.actiontable = actiontable.clone();
159 dt.actions = actions.clone();
160 dt.actionsComment = actionsComment.clone();
161 dt.actionsPostfix = actionsPostfix.clone();
162 dt.ractions = ractions.clone();
163 dt.rinitialActions = rinitialActions.clone();
164 dt.initialActions = initialActions.clone();
165 dt.initialActionsComment = initialActionsComment.clone();
166 dt.initialActionsPostfix = initialActionsPostfix.clone();
167 dt.contexts = contexts.clone();
168 dt.contextsPostfix = contextsPostfix.clone();
169 dt.contextsrc = contextsrc;
170 dt.rcontext = rcontext.clone(s);
171 return dt;
174 * Changes the type of the given decision table. The table is rebuilt.
175 * @param type
176 * @return Returns a list of errors which occurred when the type was changed.
178 public void setType(Type type) {
179 this.type = type;
182 * This routine compiles the Context statements for the
183 * decision table into a single executable array.
184 * It must embed into this array a call to executeTable
185 * (which avoids this context building for the table).
187 private void buildContexts(){
188 // Nothing to do if no extra contexts are specfied.
189 if(contextsPostfix==null || contextsPostfix.length==0) return;
191 // This is the call to executeTable against this decisiontable
192 // that we are going to embed into our executable array.
193 contextsrc = "/"+getName().stringValue()+" executeTable ";
195 boolean keep = false;
196 for(int i=contextsPostfix.length-1;i>=0;i--){
197 if(contextsPostfix[i]!=null){
198 contextsrc = "{ "+contextsrc+" } "+contextsPostfix[i];
199 keep = true;
202 if(keep == true){
203 try {
204 rcontext = RString.compile(ruleset, contextsrc, true);
205 } catch (RulesException e) {
206 errorlist.add(
207 new CompilerError (
208 ICompilerError.Type.CONTEXT,
209 "Formal Compiler Error: "+e,
210 contextsrc,0));
216 * Build this decision table according to its type.
219 public void build(){
220 errorlist.clear();
221 decisiontree = null;
222 buildContexts();
223 /**
224 * If a context or contexts are specified for this decision table,
225 * compile the context formal into postfix.
227 type.build(this);
231 * Return the name of this decision table.
232 * @return
234 public RName getName(){
235 return dtname;
239 * Renames this decision table.
240 * @param session
241 * @param newname
242 * @throws RulesException
244 public void rename(IRSession session, RName newname)throws RulesException{
245 ruleset.getEntityFactory(session).deleteDecisionTable(dtname);
246 ruleset.getEntityFactory(session).newDecisionTable(newname, session);
250 * Create a Decision Table
251 * @param tables
252 * @param name
253 * @throws RulesException
256 public RDecisionTable(IRSession session, RuleSet _ruleset, String name) throws RulesException{
257 ruleset = _ruleset;
258 dtname = RName.getRName(name,true);
259 EntityFactory ef = ruleset.getEntityFactory(session);
260 RDecisionTable dttable =ef.findDecisionTable(RName.getRName(name));
261 if(dttable != null){
262 new CompilerError(CompilerError.Type.TABLE,"Duplicate Decision Tables Found",0,0);
267 * Compile each condition and action. We mark the decision table as
268 * uncompiled if any error is detected. However, we still attempt to
269 * compile all conditions and all actions.
271 public List<ICompilerError> compile(){
272 compiled = true; // Assume the compile will work.
273 rconditions = new IRObject[conditionsPostfix.length];
274 ractions = new IRObject[actionsPostfix.length];
275 rinitialActions = new IRObject[initialActionsPostfix.length];
277 for(int i=0; i< initialActions.length; i++){
278 try {
279 rinitialActions[i] = RString.compile(ruleset, initialActionsPostfix[i],true);
280 } catch (Exception e) {
281 errorlist.add(
282 new CompilerError(
283 ICompilerError.Type.INITIALACTION,
284 "Postfix Interpretation Error: "+e,
285 initialActionsPostfix[i],
289 compiled = false;
290 rinitialActions[i]=RNull.getRNull();
294 for(int i=0;i<rconditions.length;i++){
295 try {
296 rconditions[i]= RString.compile(ruleset, conditionsPostfix[i],true);
297 } catch (RulesException e) {
298 errorlist.add(
299 new CompilerError(
300 ICompilerError.Type.CONDITION,
301 "Postfix Interpretation Error: "+e,
302 conditionsPostfix[i],
306 compiled=false;
307 rconditions[i]=RNull.getRNull();
310 for(int i=0;i<ractions.length;i++){
311 try {
312 ractions[i]= RString.compile(ruleset, actionsPostfix[i],true);
313 } catch (RulesException e) {
314 errorlist.add(
315 new CompilerError(
316 ICompilerError.Type.ACTION,
317 "Postfix Interpretation Error: "+e,
318 actionsPostfix[i],
322 compiled=false;
323 ractions[i]=RNull.getRNull();
326 return errorlist;
329 public void execute(DTState state) throws RulesException {
330 RDecisionTable last = state.getCurrentTable();
331 state.setCurrentTable(this);
332 try {
333 int estk = state.edepth();
334 int dstk = state.ddepth();
335 int cstk = state.cdepth();
337 state.pushframe();
339 if(rcontext==null){
340 executeTable(state);
341 }else{
342 if(state.testState(DTState.TRACE)){
343 state.traceTagBegin("context", "execute='"+contextsrc+"'");
344 try {
345 rcontext.execute(state);
346 } catch (RulesException e) {
347 e.setSection("Context", 0);
348 throw e;
350 state.traceTagEnd("context", null);
351 }else{
352 rcontext.execute(state);
355 state.popframe();
357 if(estk!= state.edepth() ||
358 dstk!= state.ddepth() ||
359 cstk!= state.cdepth() ){
360 throw new RulesException("Stacks Not balanced","DecisionTables",
361 "Error while executing table: "+getName().stringValue() +"\n" +
362 (estk!= state.edepth() ? "Entity Stack before "+estk+" after "+state.edepth()+"\n":"")+
363 (dstk!= state.ddepth() ? "Data Stack before "+dstk+" after "+state.ddepth()+"\n":"")+
364 (cstk!= state.cdepth() ? "Control Stack before "+cstk+" after "+state.cdepth()+"\n":""));
366 } catch (RulesException e) {
367 e.addDecisionTable(this.getName().stringValue(), this.getFilename());
368 state.setCurrentTable(last);
369 throw e;
371 state.setCurrentTable(last);
375 * A decision table is executed by simply executing the
376 * binary tree underneath the table.
378 public void executeTable(DTState state) throws RulesException {
379 if(compiled==false){
380 throw new RulesException(
381 "UncompiledDecisionTable",
382 "RDecisionTable.execute",
383 "Attempt to execute an uncompiled decision table: "+dtname.stringValue()
387 boolean trace = state.testState(DTState.TRACE);
388 int edepth = state.edepth(); // Get the initial depth of the entity stack
389 // so we can toss any extra entities added...
390 if(trace){
391 state.traceTagBegin("decisiontable","tID='"+state.tracePt()+"' name='"+dtname+"'");
392 if(state.testState(DTState.VERBOSE)){
393 state.traceTagBegin("entity_stack", null);
394 for(int i=0;i<state.edepth();i++){
395 state.traceInfo("entity", "id='"+state.getes(i).getID()+"'", state.getes(i).stringValue());
397 state.traceTagEnd("entity_stack",null);
399 state.traceTagBegin("initialActions", null);
400 for( int i=0; rinitialActions!=null && i<rinitialActions.length; i++){
401 try{
402 rinitialActions[i].execute(state);
403 }catch(RulesException e){
404 e.setSection("Initial Actions", i+1);
405 throw e;
408 state.traceTagEnd("initialActions", null);
409 if(decisiontree!=null)decisiontree.execute(state);
410 state.traceTagEnd ("decisiontable",null);
412 }else{
413 for( int i=0; rinitialActions!=null && i<rinitialActions.length; i++){
414 state.setCurrentTableSection("InitialActions", i);
415 try{
416 rinitialActions[i].execute(state);
417 }catch(RulesException e){
418 e.setSection("Initial Actions", i+1);
419 throw e;
422 if(decisiontree!=null)decisiontree.execute(state);
424 while(state.edepth() > edepth)state.entitypop(); // Pop off extra entities.
428 * Builds (if necessary) the internal representation of the decision table,
429 * then validates that structure.
430 * @return true if the structure builds and is valid; false otherwise.
432 public List<ICompilerError> getErrorList() {
433 if(decisiontree==null){
434 errorlist.clear();
435 build();
437 return errorlist;
444 * Builds the decision tree, which is a binary tree of "DTNode"'s which can be executed
445 * directly. This defines the execution of a Decision Table.
446 * <br><br>
447 * The way we build this binary tree is we walk down each column, tracing
448 * that column's path through the decision tree. Once we are at the end of the column,
449 * we add on the actions. This algorithm assumes that a decision table describes
450 * a complete decision tree, i.e. there is no set of posible condition states which
451 * are not explicitly handled by the decision table.
454 void buildBalanced() {
455 compile();
456 if(conditiontable[0].length == 0 || // If we have no conditions, or
457 conditiontable[0][0].equals("*")){ // If *, we just execute all actions
458 decisiontree = ANode.newANode(this,0); // checked in the first column
459 return;
462 decisiontree = new CNode(this,0,0, rconditions[0]); // Allocate a root node.
464 for(int col=0;col<maxcol;col++){ // For each column, we are going to run down the
465 // column building that path through the tree.
466 boolean laststep = conditiontable[0][col].equalsIgnoreCase("y"); // Set the test for the root condition.
467 CNode last = (CNode) decisiontree; // The last node will start as the root.
469 for(int i=1; i<conditiontable.length; i++){ // Now go down the rest of the conditions.
470 String t = conditiontable[i][col]; // Get this conditions truth table entry.
471 boolean yes = t.equalsIgnoreCase("y");
472 boolean invalid = false;
473 if(yes || t.equalsIgnoreCase("n")){ // If this condition should be considered...
474 CNode here=null;
475 try {
476 if(laststep){
477 here = (CNode) last.iftrue;
478 }else{
479 here = (CNode) last.iffalse;
481 if(here == null){ // Missing a CNode? Create it!
482 here = new CNode(this,col,i,rconditions[i]);
483 if(laststep){
484 last.iftrue = here;
485 }else{
486 last.iffalse = here;
489 } catch (RuntimeException e) {
490 invalid = true;
492 if(invalid || here.conditionNumber != i ){
493 errorlist.add(
494 new CompilerError(
495 ICompilerError.Type.TABLE,
496 "Condition Table Compile Error ",
497 i,col
500 return;
502 last = here;
503 laststep = yes;
506 if(laststep){ // Once we have traced the column, add the actions.
507 last.iftrue=ANode.newANode(this,col);
508 }else{
509 last.iffalse=ANode.newANode(this,col);
513 DTNode.Coordinate rowCol = decisiontree.validate();
514 if(rowCol!=null){
515 errorlist.add(
516 new CompilerError(ICompilerError.Type.TABLE,"Condition Table isn't balanced.",rowCol.row,rowCol.col)
518 compiled = false;
522 boolean newline=true;
524 private void printattrib(PrintStream p, String tag, String body){
525 if(!newline){p.println();}
526 p.print("<"); p.print(tag); p.print(">");
527 p.print(body);
528 p.print("</"); p.print(tag); p.print(">");
529 newline = false;
532 private void openTag(PrintStream p,String tag){
533 if(!newline){p.println();}
534 p.print("<"); p.print(tag); p.print(">");
535 newline=false;
539 * Write the XML representation of this decision table to the given outputstream.
540 * @param o Output stream where the XML for this decision table will be written.
542 public void writeXML(PrintStream p){
543 p.println("<decision_table>");
544 newline = true;
545 printattrib(p,"table_name",dtname.stringValue());
546 Iterator<RName> ifields = fields.keySet().iterator();
547 while(ifields.hasNext()){
548 RName name = ifields.next();
549 printattrib(p,name.stringValue(),fields.get(name));
551 openTag(p, "conditions");
552 for(int i=0; i< conditions.length; i++){
553 openTag(p, "condition_details");
554 printattrib(p,"condition_number",(i+1)+"");
555 printattrib(p,"condition_description",GenericXMLParser.encode(conditions[i]));
556 printattrib(p,"condition_postfix",GenericXMLParser.encode(conditionsPostfix[i]));
557 printattrib(p,"condition_comment",GenericXMLParser.encode(conditionsComment[i]));
558 p.println();
559 newline=true;
560 for(int j=0; j<maxcol; j++){
561 p.println("<condition_column column_number=\""+(j+1)+"\" column_value=\""+conditiontable[i][j]+"\" />");
563 p.println("</condition_details>");
565 p.println("</conditions>");
566 openTag(p, "actions");
567 for(int i=0; i< actions.length; i++){
568 openTag(p, "action_details");
569 printattrib(p,"action_number",(i+1)+"");
570 printattrib(p,"action_description",GenericXMLParser.encode(actions[i]));
571 printattrib(p,"action_postfix",GenericXMLParser.encode(actionsPostfix[i]));
572 printattrib(p,"action_comment",GenericXMLParser.encode(actionsComment[i]));
573 p.println();
574 newline=true;
575 for(int j=0; j<maxcol; j++){
576 if(actiontable[i][j].length()>0){
577 p.println("<action_column column_number=\""+(j+1)+"\" column_value=\""+actiontable[i][j]+"\" />");
580 p.println("</action_details>");
582 p.println("</actions>");
583 p.println("</decision_table>");
588 * All Decision Tables are executable.
590 public boolean isExecutable() {
591 return true;
595 * The string value of the decision table is simply its name.
597 public String stringValue() {
598 String number = fields.get("ipad_id");
599 if(number==null)number = "";
600 return number+" "+dtname.stringValue();
604 * The string value of the decision table is simply its name.
606 public String toString() {
607 return stringValue();
611 * Return the postFix value
613 public String postFix() {
614 return dtname.stringValue();
618 * The type is Decision Table.
620 public int type() {
621 return iDecisiontable;
625 * @return the actions
627 public String[] getActions() {
628 return actions;
632 * @return the actiontable
634 public String[][] getActiontable() {
635 return actiontable;
639 * @return the conditions
641 public String[] getConditions() {
642 return conditions;
646 * @return the conditiontable
648 public String[][] getConditiontable() {
649 return conditiontable;
652 public String getDecisionTableId(){
653 return fields.get(RName.getRName("table_number"));
656 public void setDecisionTableId(String decisionTableId){
657 fields.put(RName.getRName("table_number"),decisionTableId);
660 public String getPurpose(){
661 return fields.get(RName.getRName("purpose"));
664 public void setPurpose(String purpose){
665 fields.put(RName.getRName("purpose"),purpose);
668 public String getComments(){
669 return fields.get(RName.getRName("comments"));
672 public void setComments(String comments){
673 fields.put(RName.getRName("comments"),comments);
676 public String getReference(){
677 return fields.get(RName.getRName("policy_reference"));
680 public void setReference(String reference){
681 fields.put(RName.getRName("policy_reference"),reference);
685 * @return the dtname
687 public String getDtname() {
688 return dtname.stringValue();
693 * @return the ractions
695 public IRObject[] getRactions() {
696 return ractions;
699 * @param ractions the ractions to set
701 public void setRactions(IRObject[] ractions) {
702 this.ractions = ractions;
706 * @return the rconditions
708 public IRObject[] getRconditions() {
709 return rconditions;
713 * @param rconditions the rconditions to set
715 public void setRconditions(IRObject[] rconditions) {
716 this.rconditions = rconditions;
720 * @param actions the actions to set
722 public void setActions(String[] actions) {
723 this.actions = actions;
727 * @param actiontable the actiontable to set
729 public void setActiontable(String[][] actiontable) {
730 this.actiontable = actiontable;
734 * @param conditions the conditions to set
736 public void setConditions(String[] conditions) {
737 this.conditions = conditions;
741 * @param conditiontable the conditiontable to set
743 public void setConditiontable(String[][] conditiontable) {
744 this.conditiontable = conditiontable;
749 * @return the actionsComment
751 public final String[] getActionsComment() {
752 return actionsComment;
757 * @param actionsComment the actionsComment to set
759 public final void setActionsComment(String[] actionsComment) {
760 this.actionsComment = actionsComment;
765 * @return the actionsPostfix
767 public final String[] getActionsPostfix() {
768 return actionsPostfix;
773 * @param actionsPostfix the actionsPostfix to set
775 public final void setActionsPostfix(String[] actionsPostfix) {
776 this.actionsPostfix = actionsPostfix;
781 * @return the conditionsComment
783 public final String[] getConditionsComment() {
784 return conditionsComment;
789 * @param conditionsComment the conditionsComment to set
791 public final void setConditionsComment(String[] conditionsComment) {
792 this.conditionsComment = conditionsComment;
797 * @return the conditionsPostfix
799 public final String[] getConditionsPostfix() {
800 return conditionsPostfix;
805 * @param conditionsPostfix the conditionsPostfix to set
807 public final void setConditionsPostfix(String[] conditionsPostfix) {
808 this.conditionsPostfix = conditionsPostfix;
812 * A little helpper function that inserts a new column in a table
813 * of strings organized as String table [row][column]; Inserts blanks
814 * in all new entries, so this works for both conditions and actions.
815 * @param table
816 * @param col
818 private static void insert(String[][]table, int maxcol, final int col){
819 for(int i=0; i<maxcol; i++){
820 for(int j=15; j> col; j--){
821 table[i][j]=table[i][j-1];
823 table[i][col]=" ";
827 * Insert a new column at the given column number (Zero based)
828 * @param col The zero based column number for the new column
829 * @throws RulesException
831 public void insert(int col) throws RulesException {
832 if(maxcol>=16){
833 throw new RulesException("TableTooBig","insert","Attempt to insert more than 16 columns in a Decision Table");
835 insert(conditiontable,maxcol,col);
836 insert(actiontable,maxcol,col);
840 * Balances an unbalanced decision table. The additional columns have
841 * no actions added. There are two approaches to balancing tables. One
842 * is to have executed all columns whose conditions are met. The other is
843 * to execute only the first column whose conditions are met. This
844 * routine executes all columns whose conditions are met.
846 public void buildUnbalanced(UnbalancedType type) {
848 compile();
850 if(
851 conditiontable.length == 0 ||
852 conditiontable[0].length == 0 || // If we have no conditions, or
853 conditiontable[0][0].equals("*")){ // If *, we just execute all actions
854 decisiontree = ANode.newANode(this,0); // checked in the first column
855 return;
858 if(conditions.length<1){
859 errorlist.add(
860 new CompilerError(
861 ICompilerError.Type.CONDITION,
862 "You have to have at least one condition in a decision table",
863 0,0)
866 if( conditiontable[0].length==0 || conditiontable[0][0].trim().equals("*"))return;
870 CNode top = new CNode(this,1,0,this.rconditions[0]);
871 int defaultCol = -1; // The Index of the Default Column
872 int allCol = -1; // The Index of the "All" Column (executed on all conditions)
873 for(int col=0;col<maxcol;col++){ // Look at each column.
874 boolean nonemptycolumn = false;
875 for(int row=0; !nonemptycolumn && row<conditions.length; row++){
876 String v = conditiontable[row][col]; // Get the value from the condition table
877 nonemptycolumn = !v.equals("-") && !v.equals(" ");
879 if(nonemptycolumn){
880 try {
881 processCol(type,top,0,col); // Process all other columns.
882 } catch (Exception e) {
883 /** Any error detected is recorded in the errorlist. Nothing to do here **/
887 ANode defaults;
888 if(defaultCol >= 0){
889 defaults = ANode.newANode(this,defaultCol);
890 }else{
891 defaults = new ANode(this);
893 addDefaults(top,defaults); // Add defaults to all unmapped branches
894 if(allCol >= 0) addAll(top, ANode.newANode(this,allCol)); // Add to all branches the All actions
895 decisiontree = optimize(top); // Optimize the given tree.
899 * Replace any untouched branches in the tree with a pointer
900 * to the defaults for this table. We only replace nulls.
901 * @param node
902 * @param defaults
903 * @return
905 private DTNode addDefaults(DTNode node, ANode defaults){
906 if(node == null ) return defaults;
907 if(node instanceof ANode)return node;
908 CNode cnode = (CNode)node;
909 cnode.iffalse = addDefaults(cnode.iffalse,defaults);
910 cnode.iftrue = addDefaults(cnode.iftrue, defaults);
911 return node;
914 private void addAll(DTNode node, ANode all){
915 if(node.getClass()==ANode.class){
916 ((ANode)node).addNode(all);
917 }else{
918 addAll( ((CNode)node).iffalse ,all);
919 addAll( ((CNode)node).iftrue ,all);
924 * Replaces the given DTNode with the optimized DTNode.
925 * @param node
926 * @return
928 private DTNode optimize(DTNode node){
929 ANode opt = node.getCommonANode();
930 if(opt!=null){
931 return opt;
933 CNode cnode = (CNode) node;
934 cnode.iftrue = optimize(cnode.iftrue);
935 cnode.iffalse = optimize(cnode.iffalse);
936 if(cnode.iftrue.equalsNode(cnode.iffalse)){
937 return cnode.iftrue;
939 return cnode;
943 * Build a path through the decision tables for a particular column.
944 * This routine throws an exception, but the calling routine just ignores it.
945 * That way we don't flood the error list with lots of duplicate errors.
946 * @param here
947 * @param row
948 * @param col
949 * @return
951 private DTNode processCol(UnbalancedType code, DTNode here, int row, int col) throws Exception{
952 if(row >= conditions.length){ // Ah, end of the column!
953 ANode thisCol = ANode.newANode(this, col); // Get a new ANode for the column
955 if(here!=null && code == UnbalancedType.FIRST){ // If we execute only the First, we are done!
956 thisCol = (ANode) here;
958 if(here!=null && code == UnbalancedType.ALL){ // If Some path lead here, fold the
959 thisCol.addNode((ANode)here); // old stuff in with this column.
961 return thisCol; // Return the mix!
964 String v = conditiontable[row][col]; // Get the value from the condition table
965 boolean dcare = v.equals("-") || v.equals(" "); // Standardize Don't cares.
967 if(!v.equalsIgnoreCase("y") && !v.equalsIgnoreCase("n") && !dcare){
968 errorlist.add(
969 new CompilerError (
970 ICompilerError.Type.CONTEXT,
971 "Bad value in Condition Table '"+v+"' at row "+(row+1)+" column "+(col+1),
972 v,0));
974 if((here==null || here.getRow()!= row ) && dcare){ // If we are a don't care, but not on a row
975 return processCol(code,here,row+1,col); // that matches us, we skip this row for now.
978 if(here==null){ // If this node is null, and I need
979 here = new CNode(this,col,row,rconditions[row]); // a condition node, create it!
980 }else if (here!=null && here.getRow()!= row ){ // If this is the wrong node, and I need
981 CNode t = new CNode(this,col,row,rconditions[row]); // a condition node, create a new one and insert it.
982 t.iffalse = here; // Put the node I have on the false tree
983 t.iftrue = here.cloneDTNode(); // and its clone on the true path.
984 here = t; // Continue with the new node.
987 if(v.equalsIgnoreCase("y") || dcare){ // If 'y' or a don't care,
988 DTNode next = ((CNode) here).iftrue; // Recurse on the True Branch.
989 ((CNode) here).iftrue = processCol(code,next,row+1,col);
991 if (v.equalsIgnoreCase("n")|| dcare){ // If 'n' or a don't care,
992 DTNode next = ((CNode) here).iffalse; // Recurse on the False branch. Note that
993 ((CNode) here).iffalse = processCol(code,next,row+1,col); // Don't care branches both ways.
995 return here; // Return the Condition node.
999 * In the case of an unbalanced decision table, this method returns a balanced
1000 * decision table using one of the two unbalanced rules: FIRST (which executes only
1001 * the first column whose conditions are matched) and ALL (which executes all columns
1002 * whose conditions are matched). If the decision table is balanced, this method returns
1003 * an "optimized" decision table where all possible additional "don't cares" are inserted.
1005 * @return
1007 RDecisionTable balancedTable(IRSession session) throws RulesException{
1008 if(balanceTable==null)balanceTable = new BalanceTable(this);
1009 return balanceTable.balancedTable(session);
1012 public BalanceTable getBalancedTable() throws RulesException {
1013 return new BalanceTable(this);
1016 public Iterator<RDecisionTable> DecisionTablesCalled(){
1017 ArrayList<RDecisionTable> tables = new ArrayList<RDecisionTable>();
1018 ArrayList<RArray> stack = new ArrayList<RArray>();
1019 for(int i=0;i<ractions.length;i++){
1020 addTables(ractions[i],stack,tables);
1022 return tables.iterator();
1025 private void addTables(IRObject action,List<RArray> stack, List<RDecisionTable> tables){
1026 if(action==null)return;
1027 if(action.type()==iArray){
1028 RArray array = (RArray)action;
1029 if(stack.contains(array))return; // We have already been here.
1030 stack.add(array);
1031 try { // As this is an array, arrayValue() will not ever throw an exception
1032 @SuppressWarnings({"unchecked"})
1033 Iterator objects = array.arrayValue().iterator();
1034 while(objects.hasNext()){
1035 addTables((IRObject) objects.next(),stack,tables);
1037 } catch (RulesException e) { }
1039 if(action.type()==iDecisiontable && !tables.contains(action)){
1040 tables.add((RDecisionTable)action);
1044 * Returns the list of Decision Tables called by this Decision Table
1045 * @return
1047 ArrayList<RDecisionTable> decisionTablesCalled(){
1048 ArrayList<RDecisionTable> calledTables = new ArrayList<RDecisionTable>();
1050 addlist(calledTables, rinitialActions);
1051 addlist(calledTables, rconditions);
1052 addlist(calledTables, ractions);
1054 return calledTables;
1057 * We do a recursive search down each IRObject in these lists, looking for
1058 * references to Decision Tables. We only add references to Decision Tables
1059 * to the list of called tables if the list of called tables doesn't yet have
1060 * that reference.
1062 * @param calledTables
1063 * @param list
1065 private void addlist(ArrayList<RDecisionTable> calledTables, IRObject [] list){
1066 for(int i=0; i<list.length; i++){
1067 ArrayList<RDecisionTable> tables = new ArrayList<RDecisionTable>();
1068 ArrayList<RArray> stack = new ArrayList<RArray>();
1069 getTables(stack, tables, list[i]);
1070 for(RDecisionTable table : tables){
1071 if(!calledTables.contains(table))calledTables.add(table);
1076 * Here we do a recursive search of all the constructs in an IROBject. This
1077 * is because some IRObjects are arrays, so we search them as well.
1078 * @param obj
1079 * @return
1081 private ArrayList<RDecisionTable> getTables(ArrayList<RArray>stack, ArrayList<RDecisionTable> tables, IRObject obj){
1083 if(obj instanceof RDecisionTable) tables.add((RDecisionTable) obj);
1084 if(obj instanceof RArray && !stack.contains(obj)){
1085 stack.add((RArray) obj);
1086 for(IRObject obj2 : (RArray) obj){
1087 getTables(stack,tables,obj2);
1090 return tables;