4 * Copyright 2004-2007 MTBJ, Inc.
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
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.
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
;
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
;
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>
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
;
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
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.
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() {
145 public void setFilename(String filename
) {
146 this.filename
= filename
;
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
);
174 * Changes the type of the given decision table. The table is rebuilt.
176 * @return Returns a list of errors which occurred when the type was changed.
178 public void setType(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
];
204 rcontext
= RString
.compile(ruleset
, contextsrc
, true);
205 } catch (RulesException e
) {
208 ICompilerError
.Type
.CONTEXT
,
209 "Formal Compiler Error: "+e
,
216 * Build this decision table according to its type.
224 * If a context or contexts are specified for this decision table,
225 * compile the context formal into postfix.
231 * Return the name of this decision table.
234 public RName
getName(){
239 * Renames this decision table.
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
253 * @throws RulesException
256 public RDecisionTable(IRSession session
, RuleSet _ruleset
, String name
) throws RulesException
{
258 dtname
= RName
.getRName(name
,true);
259 EntityFactory ef
= ruleset
.getEntityFactory(session
);
260 RDecisionTable dttable
=ef
.findDecisionTable(RName
.getRName(name
));
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
++){
279 rinitialActions
[i
] = RString
.compile(ruleset
, initialActionsPostfix
[i
],true);
280 } catch (Exception e
) {
283 ICompilerError
.Type
.INITIALACTION
,
284 "Postfix Interpretation Error: "+e
,
285 initialActionsPostfix
[i
],
290 rinitialActions
[i
]=RNull
.getRNull();
294 for(int i
=0;i
<rconditions
.length
;i
++){
296 rconditions
[i
]= RString
.compile(ruleset
, conditionsPostfix
[i
],true);
297 } catch (RulesException e
) {
300 ICompilerError
.Type
.CONDITION
,
301 "Postfix Interpretation Error: "+e
,
302 conditionsPostfix
[i
],
307 rconditions
[i
]=RNull
.getRNull();
310 for(int i
=0;i
<ractions
.length
;i
++){
312 ractions
[i
]= RString
.compile(ruleset
, actionsPostfix
[i
],true);
313 } catch (RulesException e
) {
316 ICompilerError
.Type
.ACTION
,
317 "Postfix Interpretation Error: "+e
,
323 ractions
[i
]=RNull
.getRNull();
329 public void execute(DTState state
) throws RulesException
{
330 RDecisionTable last
= state
.getCurrentTable();
331 state
.setCurrentTable(this);
333 int estk
= state
.edepth();
334 int dstk
= state
.ddepth();
335 int cstk
= state
.cdepth();
342 if(state
.testState(DTState
.TRACE
)){
343 state
.traceTagBegin("context", "execute='"+contextsrc
+"'");
345 rcontext
.execute(state
);
346 } catch (RulesException e
) {
347 e
.setSection("Context", 0);
350 state
.traceTagEnd("context", null);
352 rcontext
.execute(state
);
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
);
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
{
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...
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
++){
402 rinitialActions
[i
].execute(state
);
403 }catch(RulesException e
){
404 e
.setSection("Initial Actions", i
+1);
408 state
.traceTagEnd("initialActions", null);
409 if(decisiontree
!=null)decisiontree
.execute(state
);
410 state
.traceTagEnd ("decisiontable",null);
413 for( int i
=0; rinitialActions
!=null && i
<rinitialActions
.length
; i
++){
414 state
.setCurrentTableSection("InitialActions", i
);
416 rinitialActions
[i
].execute(state
);
417 }catch(RulesException e
){
418 e
.setSection("Initial Actions", i
+1);
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){
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.
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() {
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
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...
477 here
= (CNode
) last
.iftrue
;
479 here
= (CNode
) last
.iffalse
;
481 if(here
== null){ // Missing a CNode? Create it!
482 here
= new CNode(this,col
,i
,rconditions
[i
]);
489 } catch (RuntimeException e
) {
492 if(invalid
|| here
.conditionNumber
!= i
){
495 ICompilerError
.Type
.TABLE
,
496 "Condition Table Compile Error ",
506 if(laststep
){ // Once we have traced the column, add the actions.
507 last
.iftrue
=ANode
.newANode(this,col
);
509 last
.iffalse
=ANode
.newANode(this,col
);
513 DTNode
.Coordinate rowCol
= decisiontree
.validate();
516 new CompilerError(ICompilerError
.Type
.TABLE
,"Condition Table isn't balanced.",rowCol
.row
,rowCol
.col
)
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(">");
528 p
.print("</"); p
.print(tag
); p
.print(">");
532 private void openTag(PrintStream p
,String tag
){
533 if(!newline
){p
.println();}
534 p
.print("<"); p
.print(tag
); p
.print(">");
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>");
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
]));
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
]));
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() {
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.
621 return iDecisiontable
;
625 * @return the actions
627 public String
[] getActions() {
632 * @return the actiontable
634 public String
[][] getActiontable() {
639 * @return the conditions
641 public String
[] getConditions() {
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
);
687 public String
getDtname() {
688 return dtname
.stringValue();
693 * @return the ractions
695 public IRObject
[] getRactions() {
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() {
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.
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];
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
{
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
) {
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
858 if(conditions
.length
<1){
861 ICompilerError
.Type
.CONDITION
,
862 "You have to have at least one condition in a decision table",
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(" ");
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 **/
889 defaults
= ANode
.newANode(this,defaultCol
);
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.
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
);
914 private void addAll(DTNode node
, ANode all
){
915 if(node
.getClass()==ANode
.class){
916 ((ANode
)node
).addNode(all
);
918 addAll( ((CNode
)node
).iffalse
,all
);
919 addAll( ((CNode
)node
).iftrue
,all
);
924 * Replaces the given DTNode with the optimized DTNode.
928 private DTNode
optimize(DTNode node
){
929 ANode opt
= node
.getCommonANode();
933 CNode cnode
= (CNode
) node
;
934 cnode
.iftrue
= optimize(cnode
.iftrue
);
935 cnode
.iffalse
= optimize(cnode
.iffalse
);
936 if(cnode
.iftrue
.equalsNode(cnode
.iffalse
)){
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.
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
){
970 ICompilerError
.Type
.CONTEXT
,
971 "Bad value in Condition Table '"+v
+"' at row "+(row
+1)+" column "+(col
+1),
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.
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.
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
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
1062 * @param calledTables
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.
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
);