Package com.dtrules.decisiontables

Source Code of com.dtrules.decisiontables.RDecisionTable

/**
* Copyright 2004-2011 DTRules.com, Inc.
*
* See http://DTRules.com for updates and documentation for the DTRules Rules Engine 
*  
* Licensed under the Apache License, Version 2.0 (the "License"); 
* you may not use this file except in compliance with the License. 
* You may obtain a copy of the License at 
*  
*      http://www.apache.org/licenses/LICENSE-2.0 
*  
* Unless required by applicable law or agreed to in writing, software 
* distributed under the License is distributed on an "AS IS" BASIS, 
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 
* See the License for the specific language governing permissions and 
* limitations under the License. 
**/

 
package com.dtrules.decisiontables;

import java.io.PrintStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import com.dtrules.decisiontables.DTNode.Coordinate;
import com.dtrules.infrastructure.RulesException;
import com.dtrules.interpreter.ARObject;
import com.dtrules.interpreter.IRObject;
import com.dtrules.interpreter.RArray;
import com.dtrules.interpreter.RName;
import com.dtrules.interpreter.RNull;
import com.dtrules.interpreter.RString;
import com.dtrules.interpreter.RType;
import com.dtrules.session.DTState;
import com.dtrules.session.EntityFactory;
import com.dtrules.session.IDecisionTableError;
import com.dtrules.session.IRSession;
import com.dtrules.session.RuleSet;
import com.dtrules.xmlparser.GenericXMLParser;

/**
* Decision Tables are the classes that hold the Rules for a set of Policy
* implemented using DTRules.  There are three types: <br><br>
*
* BALANCED -- These decision tables expect all branches to be defined in the condition table <br>
* ALL      -- Evaluates all the columns, then executes all the actions, in the order they
*             are specified, for all columns whose conditions are met.<br>
* FIRST    -- Effectively evaluates each column, and executes only the first Column whose
*             conditions are met.<br>
* @author paul snow
* Mar 1, 2007
*
*/
public class RDecisionTable extends ARObject {
   
  public static RType dttype = RType.newType("decisiontable");
 
    public static final String DASH = "-";      // Using a constant reduces our footprint, and increases our speed.
   
    private final  RName    dtname;             // The decision table's name.
   
    private        String   filename = null;    // Filename of Excel file where the table is defined,
                                                //   if this decision table is defined in Excel.

    enum UnbalancedType { FIRST, ALL };         // Unbalanced Table Types.
   
    public static enum Type {
        BALANCED { void build(DTState state, RDecisionTable dt) {dt.compile(); dt.buildBalanced();                       dt.check(null);}},
        FIRST    { void build(DTState state, RDecisionTable dt) {dt.compile(); dt.buildUnbalanced(state, UnbalancedType.FIRST); dt.check(null);}},
        ALL      { void build(DTState state, RDecisionTable dt) {dt.compile(); dt.buildUnbalanced(state, UnbalancedType.ALL);   dt.check(null);}};
        abstract void build(DTState state, RDecisionTable dt);
    }   
 
    public Type  type = Type.BALANCED;          // By default, all decision tables are balanced.
   
    public static  final int MAXCOL = 16;       // The Maximum number of columns in a decision table.
   
                         int maxcol = 1;        // The number of columns in this decision table.
                            
    private final IRSession session;          // We need to compile within a session, so we know how to parse dates (among other things)
 
    private final RuleSet ruleset;          // A decision table must belong to a particular ruleset
   
    public  final Map<RName,String> fields = new HashMap<RName, String>(); // Holds meta information about this decision table.

  private boolean  compiled=false;            // The decision table isn't compiled
                        //   until fully constructed.  And
                        //   it won't be if compilation fails.

    String   [] contexts;                       // Contexts in which to execute this table.
    String   [] contextComments;                // Comments on the contexts.
    String   [] contextsPostfix;                // The Postfix for each context statement.
    String      contextsrc;                     // For Tracing...
    IRObject    rcontext;                       //  lists of entities.  It is best if this is done within the table than
                                                //  by the calling table. 

    String   [] initialActions;                 // A list of actions to be executed each time the
                                                //   decision table is executed before the conditions
                                                //   are evaluated.
    String   [] initialActionsPostfix;          // Compiled Initial Actions
    String   [] initialActionsComment;          // Comment for Initial Actions
    IRObject [] rinitialActions;                // The compiled version of the initial action

 
  String [][] conditiontable;
  String   [] conditions;                     // The conditions in formal.  This is compiled to get the postfix
  String   [] conditionsPostfix;              // The conditions in postfix. derived from the formal
  String   [] conditionsComment;              // A comment per condition.
  IRObject [] rconditions;          // Each compiled condition
 
    String [][] actiontable;                    // Indicates which actions should be executed
  String   [] actions;                        // The actions in the language specified
  String   [] actionsComment;                 // A free form comment for the action
  String   [] actionsPostfix;                 // The compiled postfix version of the action
  IRObject [] ractions;            // Each compiled action
 
  // While actions and conditions are 0 based arrays, the policy statements are 1 based.
  // The zero'th index is the default value (if no columns match).
  String   [] policystatements;               // The Policy statements as defined in the Decision Table.
  String   [] policystatementsPostfix;        // Generally, a policy statement will be a string, but can have values
  IRObject [] rpolicystatements;              // The IRObject that returns a string for a policy statement.
 
 
 
  boolean  [] columnsSpecified  = null;       // Columns defined in the XML
  boolean  [] columnsUsed       = null;       // These are the columns really used in the executable form of the table.
  boolean  [] conditionsUsed    = null;       // We mark conditions which might be evaluated
  boolean  [] actionsUsed       = null;       // We mark actions which might be evaluated
  boolean  [] columnUnreachable = null;       // Mark actions unreachable if they don't end up in the decision tree
  boolean     hasNullColumn     = false;      // Not all tables have a null column.  This is true if
                                              //   this table has one.
 
  int starColumn      = -1;                   // Column where a star is found (defaulted to none). 
  int otherwiseColumn = -1;
  int alwaysColumn    = -1;
 
  // Virtual field values.
    public static  RName    table_name = RName.getRName("Name");
    public static  RName    file_name = RName.getRName("File_Name");
    public static  RName    type_name = RName.getRName("type")
 
  public boolean getHasNullColumn(){
      return hasNullColumn;
  }
 
  /**
     * @return the type
     */
    public Type getType() {
        return type;
    }

    /**
     * @return the mAXCOL
     */
    public static int getMAXCOL() {
        return MAXCOL;
    }

    /**
     * @return the maxcol
     */
    public int getMaxcol() {
        return maxcol;
    }

    /**
     * @return the ruleset
     */
    public RuleSet getRuleset() {
        return ruleset;
    }

    /**
     * @return the fields
     */
    public Map<RName, String> getFields() {
        return fields;
    }

    /**
     * @return the initialActions
     */
    public String[] getInitialActions() {
        return initialActions;
    }

    /**
     * @return the rinitialActions
     */
    public IRObject[] getRinitialActions() {
        return rinitialActions;
    }

    /**
     * @return the initialActionsPostfix
     */
    public String[] getInitialActionsPostfix() {
        return initialActionsPostfix;
    }

    /**
     * @return the initialActionsComment
     */
    public String[] getInitialActionsComment() {
        return initialActionsComment;
    }
   
    /**
     *
     * @return comments on each context statement.
     */
    public String[] getContextComments() {
        return contextComments;
    }

    /**
     * @return the contexts
     */
    public String[] getContexts() {
        return contexts;
    }

    /**
     * @return the contextsPostfix
     */
    public String[] getContextsPostfix() {
        return contextsPostfix;
    }

    /**
     * @return the contextsrc
     */
    public String getContextsrc() {
        return contextsrc;
    }

    /**
     * @return the rcontext
     */
    public IRObject getRcontext() {
        return rcontext;
    }

    /**
     * @return the columnsSpecified
     */
    public boolean[] getColumnsSpecified() {
        return columnsSpecified;
    }

    /**
     * @return the columnsUsed
     */
    public boolean[] getColumnsUsed() {
        return columnsUsed;
    }

    /**
     * @return the conditionsUsed
     */
    public boolean[] getConditionsUsed() {
        return conditionsUsed;
    }

    /**
     * @return the actionsUsed
     */
    public boolean[] getActionsUsed() {
        return actionsUsed;
    }

    /**
     * @return the columnUnreachable
     */
    public boolean[] getColumnUnreachable() {
        return columnUnreachable;
    }

    /**
     * @return the errorlist
     */
    public List<IDecisionTableError> getErrorlist() {
        return errorlist;
    }

    /**
     * @return the balanceTable
     */
    public BalanceTable getBalanceTable() {
        return balanceTable;
    }

    private void whatsUsed(){
        int conditionCnt  = conditiontable.length>0?conditiontable[0].length:0;
        columnsSpecified  = new boolean [conditionCnt];
      columnsUsed       = new boolean [conditionCnt];
      columnUnreachable = new boolean [conditionCnt];
      conditionsUsed    = new boolean [conditions.length];
      actionsUsed       = new boolean [actions.length];
     
      for(int col=0; col < conditionCnt; col++){   // For each column
          for(int row=0; row < conditiontable.length; row++) { // For each row
              if(    conditiontable[row][col].equalsIgnoreCase("y")||
                     conditiontable[row][col].equalsIgnoreCase("n")||
                     conditiontable[row][col].equalsIgnoreCase("*")){
                  columnsSpecified[col]    = true;
              }
          }
                  // A bit tricky.  We LOOK at columns that seem to be used.
                    //  but if no actions are specified, we don't worry about them.
                    //  This is because dead columns will be dropped when we
                    //  balance tables, and we will throw a bogus error if we don't
                    //  ignore such columns here.
  
          for(int row=0; row < actions.length; row++){
              if( columnsSpecified[col] && actiontable[row][col].equalsIgnoreCase("x")){
                  columnsUsed[col]=true;                  // If there is an action specified, return it to the used
                  actionsUsed[row]=true;                  //   category (and mark the action as used as well).
              }
          }
      }
  } 
 
  private void setUnreachable(){
      for(int i=0; i < columnsUsed.length; i++){         // We assume the worst... If we are using the column,
          columnUnreachable[i]=columnsUsed[i];           //  then we assume it is unreachable until proven
      }                                                  //  otherwise.
      setUnreachable(decisiontree);                      // Now go look for each column in the decisiontree!
  }
 
  private void setUnreachable(DTNode node){              // This is a recursive search for each column...
      if(node == null ) return;                          // Somebody else's problem if nulls are in the tree...
      if(node instanceof CNode){                         // If a condition node, recurse...
            setUnreachable( ((CNode)node).iftrue);         //    look at the true branches...
            setUnreachable( ((CNode)node).iffalse);        //    look at the false branches...
            conditionsUsed[((CNode)node).conditionNumber] = true;   // And set that we might actually need to evaluate
      }                                                  //             this condition.
      if(node instanceof ANode){                         // If this is an Action Node... Look at its columns!   
          for (int col : ((ANode)node).columns){         // Simply grab the columns that might lead to this action...
              if(col <= columnUnreachable.length){       //    and mark them as reachable (i.e. not unreachable)
                  columnUnreachable[col-1]=false;        //    (but ignore all of this if the col number is out if range)
              }
          }
          if(((ANode)node).columns.size()==0) {          // If no column got us here, then this is a null column
              hasNullColumn = true;
          }
          for (int action : ((ANode)node).anumbers){     // Mark all the actions in this column as used.
              actionsUsed[action]=true;
          }
      }
     
  }
 
 
 
  List<IDecisionTableError> errorlist = new ArrayList<IDecisionTableError>();
  DTNode decisiontree=null;

    private int numberOfRealColumns = 0;        // Number of real columns (as unbalanced tables can have
    // far more columns than they appear to have).

    public int getNumberOfRealColumns() {
        if(decisiontree==null)return 0;
        return decisiontree.countColumns();
    }

    /**
     * Check for errors in the decision table.  Returns the column
     * and row of a problem if one is found.  If nothing is wrong,
     * a null is returned.
     * @return
     */
    public Coordinate validate(){
       if(decisiontree==null){
           if(actions !=null && actions.length==0)return null;
            return new Coordinate(0,0);
       }
       return decisiontree.validate();
    }
   
    BalanceTable balanceTable = null;           // Helper class to build a balanced or optimized version
                                                //   of this decision table.
   
    public boolean isCompiled(){return compiled;}
   
   
   
  public String getFilename() {
        return filename;
    }

    public void setFilename(String filename) {
        this.filename = filename;
        fields.put(file_name,filename);
    }

    @Override
    public IRObject clone(IRSession s) throws RulesException {
        RDecisionTable dt = new RDecisionTable(s, dtname.stringValue());
       
        dt.numberOfRealColumns      = numberOfRealColumns;
       
        dt.contexts                 = contexts.clone();
        dt.contextsPostfix          = contextsPostfix.clone();
        dt.contextsrc               = contextsrc;
        dt.rcontext                 = rcontext.clone(s);
       
        dt.rinitialActions          = rinitialActions.clone();
        dt.initialActions           = initialActions.clone();
        dt.initialActionsComment    = initialActionsComment.clone();
        dt.initialActionsPostfix    = initialActionsPostfix.clone();
       
        dt.conditiontable           = conditiontable.clone();
        dt.conditions               = conditions.clone();
        dt.conditionsPostfix        = conditionsPostfix.clone();
        dt.conditionsComment        = conditionsComment.clone();
        dt.rconditions              = rconditions.clone();
       
        dt.actiontable              = actiontable.clone();
        dt.actions                  = actions.clone();
        dt.actionsComment           = actionsComment.clone();
        dt.actionsPostfix           = actionsPostfix.clone();
        dt.ractions                 = ractions.clone();
       
        dt.policystatements         = policystatements.clone();
        dt.policystatementsPostfix  = policystatementsPostfix.clone();
        dt.rpolicystatements        = rpolicystatements.clone();
       
        return dt;
    }
    /**
     * Changes the type of the given decision table.  The table is rebuilt.
     * @param type
     * @return Returns a list of errors which occurred when the type was changed.
   */
  public void setType(Type type) {
       this.type = type;  
    }
  /**
   * This routine compiles the Context statements for the
   * decision table into a single executable array. 
   * It must embed into this array a call to executeTable
   * (which avoids this context building for the table).
   */
  private void buildContexts(){
      // Nothing to do if no extra contexts are specfied.
     if(contextsPostfix==null || contextsPostfix.length==0) return;
      
     // This is the call to executeTable against this decisiontable
     // that we are going to embed into our executable array.
     contextsrc = "/"+getName().stringValue()+" executeTable ";
      
     boolean keep = false;
       for(int i=contextsPostfix.length-1;i>=0;i--){
           if(contextsPostfix[i]!=null){
               contextsrc = "{ "+contextsrc+" } "+contextsPostfix[i];
               keep = true;
           }   
       }
       if(keep == true){
           try {
              rcontext = RString.compile(session, contextsrc, true);
           } catch (RulesException e) {
              errorlist.add(
                    new CompilerError (
                            IDecisionTableError.Type.CONTEXT,
                            "Formal Compiler Error: "+e,
                            contextsrc,0));
           }
       }       
  }
   
    /**
     * Build this decision table according to its type.
     *
     */
  public void build(DTState state){
       errorlist.clear();
       decisiontree = null;
       buildContexts();
       /**
        * If a context or contexts are specified for this decision table,
        * compile the context formal into postfix.
        */
       type.build(state, this);
    }
       
    /**
     * Return the name of this decision table.
     * @return
     */
    public RName getName(){
        return dtname;
    }
   
    /**
     * Renames this decision table.
     * @param session
     * @param newname
     * @throws RulesException
     */
    public void rename(IRSession session, RName newname)throws RulesException{
        ruleset.getEntityFactory(session).deleteDecisionTable(dtname);
        ruleset.getEntityFactory(session).newDecisionTable(newname, session);
    }
   
    /**
     * Create a Decision Table
     * @param tables
     * @param name
     * @throws RulesException
     */
   
  public RDecisionTable(IRSession session, String name) throws RulesException{
        this.session = session;
        ruleset      = session.getRuleSet();
    dtname       = RName.getRName(name,true);
   
        EntityFactory ef = ruleset.getEntityFactory(session);
        RDecisionTable dttable =ef.findDecisionTable(RName.getRName(name));
        if(dttable != null){
            new CompilerError(CompilerError.Type.TABLE,"Duplicate Decision Tables Found",0,0);
    }   
  }
 
  /**
   * Compile each condition and action.  We mark the decision table as
   * uncompiled if any error is detected.  However, we still attempt to
   * compile all conditions and all actions.
   */
  public List<IDecisionTableError> compile(){
      try{
        compiled          = true;                  // Assume the compile will work.   
        rconditions       = new IRObject[conditionsPostfix.length];
        ractions          = new IRObject[actionsPostfix.length];
        rinitialActions   = new IRObject[initialActionsPostfix.length];
        rpolicystatements = new IRObject[policystatementsPostfix.length];
       
        for(int i=0; i< initialActions.length; i++){
                 try {
                     rinitialActions[i] = RString.compile(session, initialActionsPostfix[i],true);
                 } catch (Exception e) {
                     errorlist.add(
                             new CompilerError(
                                IDecisionTableError.Type.INITIALACTION,
                                "Postfix Interpretation Error: "+e,
                                initialActionsPostfix[i],
                                i
                             )
                     );           
                     compiled = false;
                     rinitialActions[i]=RNull.getRNull();
                 }
             }
        
        for(int i=0;i<rconditions.length;i++){
          try {
            rconditions[i]= RString.compile(session, conditionsPostfix[i],true);
          } catch (RulesException e) {
                    errorlist.add(
                       new CompilerError(
                          IDecisionTableError.Type.CONDITION,
                          "Postfix Interpretation Error: "+e,
                          conditionsPostfix[i],
                          i
                       )
                    );
                    compiled=false;
            rconditions[i]=RNull.getRNull();
          }
        }
        for(int i=0;i<ractions.length;i++){
          try {
            ractions[i]= RString.compile(session, actionsPostfix[i],true);
          } catch (RulesException e) {
                    errorlist.add(
                            new CompilerError(
                               IDecisionTableError.Type.ACTION,
                               "Postfix Interpretation Error: "+e,
                               actionsPostfix[i],
                               i
                            )
                         );
                    compiled=false;
            ractions[i]=RNull.getRNull();
          }
        }
       
        for(int i=0;i<policystatementsPostfix.length;i++){
                try {
                    rpolicystatements[i]= RString.compile(session, policystatementsPostfix[i],true);
                } catch (RulesException e) {
                    errorlist.add(
                            new CompilerError(
                               IDecisionTableError.Type.POLICYSTATEMENT,
                               "Postfix Interpretation Error: "+e,
                               policystatementsPostfix[i],
                               i
                            )
                         );
                    compiled=false;
                    rpolicystatements[i]=RNull.getRNull();
                }
            }
       
      }catch(Exception e){
            errorlist.add(
                    new CompilerError(
                       IDecisionTableError.Type.TABLE,
                       "Unexpected Exception Thrown: "+e,
                       0,
                       0
                    )
                 );
     
   
      }
    return errorlist;
  }
 
  /**
   * Checks the compile of this decision table, setting the columns used and
   * looks for unreachable columns.
   */
  public void check(PrintStream out){
   
      whatsUsed();
    setUnreachable();
   
        boolean header = false;

    for(int i=0; i< columnUnreachable.length; i++){
        if(columnUnreachable[i]){
            if(out!= null && header == false){
                    out.println (getName().stringValue());
                    header = true;
                }
                if(out!=null)out.println("  *** Column "+(i+1)+" cannot be reached.");
               
            //errorlist.add(                              // Print warnings about unreachable code.
                //        new CompilerError(
                //                ICompilerError.Type.TABLE,
                //                "Column "+(i+1)+" cannot be reached.",
                //                0,i
                //             )
                //        );
        }
    }
       
    for(int i=0; i< conditionsUsed.length; i++){
        if(rconditions[i]!=null && conditionsUsed[i]==false){
            if(out!= null && header == false){
                out.println (getName().stringValue());
                header = true;
            }
            if(out!=null)out.println("      condition "+(i+1)+" is not used");
        }
    }
   
    for(int i=0; i< actionsUsed.length; i++){
            if(ractions[i]!=null && actionsUsed[i]==false){
                if(out != null && header == false){
                    out.println (getName().stringValue());
                    header = true;
                }
                if(out!=null)out.println("      action "+(i+1)+" is not used");
            }
        }
  }
 
  public void execute(DTState state) throws RulesException {
      RDecisionTable last = state.getCurrentTable();
      state.setCurrentTable(this);
      state.traceTagBegin("decisiontable","name",dtname.stringValue());
      try {
      int estk     = state.edepth();
      int dstk     = state.ddepth();
      int cstk     = state.cdepth();

      state.pushframe();
     
      if(rcontext==null){
          if(state.testState(DTState.TRACE)){
              try{
                  state.traceTagBegin("setup");
                     executeTable(state);
                  state.traceTagEnd();
              }catch(RulesException e){
                  state.traceTagEnd();
                  throw e;
              }
          }else{
              executeTable(state);
          }
         
      }else{
          if(state.testState(DTState.TRACE)){
              state.traceTagBegin("context", "execute",contextsrc);
             
              for(String context : this.contexts){
                  if(context != null && context.trim().length()>0){
                      state.traceInfo("formal",context);
                  }
              }
              state.traceTagBegin("setup");
              try {
                        rcontext.execute(state);
                    } catch (RulesException e) {
                        state.traceTagEnd();
                        state.traceTagEnd();
                        e.setSection("Context", 0);
                        throw e;
                    }
                    state.traceTagEnd();
              state.traceTagEnd();
          }else{
              rcontext.execute(state);
          }   
      }
      state.popframe();
     
      if(estk!= state.edepth() ||
         dstk!= state.ddepth() ||
         cstk!= state.cdepth() ){
          throw new RulesException("Stacks Not balanced","DecisionTables",
          "Error while executing table: "+getName().stringValue() +"\n" +
           (estk!= state.edepth() ? "Entity Stack before  "+estk+" after "+state.edepth()+"\n":"")+
           (dstk!= state.ddepth() ? "Data Stack before    "+dstk+" after "+state.ddepth()+"\n":"")+
           (cstk!= state.cdepth() ? "Control Stack before "+cstk+" after "+state.cdepth()+"\n":""));
      }
    } catch (RulesException e) {
          state.traceTagEnd();
      e.addDecisionTable(this.getName().stringValue(), this.getFilename());
      state.setCurrentTable(last);
      throw e;
    }
    state.traceTagEnd();
      state.setCurrentTable(last);
  }
 
  /**
   * A decision table is executed by simply executing the
   * binary tree underneath the table.
   */
  public void executeTable(DTState state) throws RulesException {
        if(compiled==false){
            throw new RulesException(
                "UncompiledDecisionTable",
                "RDecisionTable.execute",
                "Attempt to execute an uncompiled decision table: "+dtname.stringValue()
            );
        }
       
        boolean trace = state.testState(DTState.TRACE);
        int edepth    = state.edepth()// Get the initial depth of the entity stack
                                         //  so we can toss any extra entities added...
        if(trace){
            state.traceTagEnd();
            if(state.testState(DTState.VERBOSE)){
                state.traceTagBegin("entity_stack");
                for(int i=0;i<state.edepth();i++){
                    state.traceInfo("entity", "id",state.getes(i).getID()+"", state.getes(i).stringValue());
                }
                state.traceTagEnd();
            }
            state.traceTagBegin("initialActions");
            for( int i=0; rinitialActions!=null && i<rinitialActions.length; i++){
                try{
                   state.traceTagBegin("initialAction");
                   state.traceInfo("formal",initialActions[i]);
                   rinitialActions[i].execute(state);
                   state.traceTagEnd();
                }catch(RulesException e){
                    e.setSection("Initial Actions", i+1);
                    throw e;
                }
            }
            state.traceTagEnd();
            if(decisiontree!=null)decisiontree.execute(state);
            state.traceTagBegin("setup");
        }else{
            for( int i=0; rinitialActions!=null && i<rinitialActions.length; i++){
                state.setCurrentTableSection("InitialActions", i);
                try{
                    rinitialActions[i].execute(state);
                 }catch(RulesException e){
                     e.setSection("Initial Actions", i+1);
                     throw e;
                 }
            }
            if(decisiontree!=null)decisiontree.execute(state);
        }   
        while(state.edepth() > edepth)state.entitypop();     // Pop off extra entities.
  }

  /**
   * Builds (if necessary) the internal representation of the decision table,
   * then validates that structure.
   * @return true if the structure builds and is valid; false otherwise.
   */
  public List<IDecisionTableError> getErrorList(DTState state)  {
       if(decisiontree==null){
           errorlist.clear();
           build(state);
     }
     return errorlist;
 
 
   
   
   
  /**
   * Builds the decision tree, which is a binary tree of "DTNode"'s which can be executed
     * directly.  This defines the execution of a Decision Table.
     * <br><br>
     * The way we build this binary tree is we walk down each column, tracing
   * that column's path through the decision tree.  Once we are at the end of the column,
   * we add on the actions.  This algorithm assumes that a decision table describes
   * a complete decision tree, i.e. there is no set of possible condition states which
     * are not explicitly handled by the decision table.
   *
   */
  void buildBalanced() {
    if(conditiontable[0].length == 0 ||           // If we have no conditions, or
       conditiontable[0][0].equals("*")){         // If *, we just execute all actions
       decisiontree = ANode.newANode(this,0);    //   checked in the first column            
       return;
    }
      
    decisiontree = new CNode(this,0,0, rconditions[0]);     // Allocate a root node.
      
        for(int col=0;col<maxcol;col++){                        // For each column, we are going to run down the
                                                                //   column building that path through the tree.
      boolean laststep = conditiontable[0][col].equalsIgnoreCase("y")// Set the test for the root condition.
      CNode   last     = (CNode) decisiontree;            // The last node will start as the root.         

      boolean star = false;                               // Once you find a star, you can't do something else.
      for(int i=1; i<conditiontable.length; i++){         // Now go down the rest of the conditions.
        String t = conditiontable[i][col];              // Get this conditions truth table entry.
       
       
        boolean yes  = t.equalsIgnoreCase("y");
        boolean no   = t.equalsIgnoreCase("n");
        if(star){
            new CompilerError(IDecisionTableError.Type.TABLE,"You can't follow a '*' with a '"+t+"' ",i,col);
        }
        star = t.equalsIgnoreCase("*");
       
        boolean invalid = false;
       
        if(yes || no ){                                // If this condition should be considered...
          CNode here=null;
          try {
            if(laststep){
              here = (CNode) last.iftrue;
            }else{
              here = (CNode) last.iffalse;
            }
           
            if(here == null){                       // Missing a CNode?  Create it!
              here = new CNode(this,col,i,rconditions[i]);
              if(laststep){
                last.iftrue  = here;
              }else{
                last.iffalse = here;
              }
            }
           
          } catch (RuntimeException e) {
                        invalid = true;       
          }
          if(invalid || here.conditionNumber != i ){
                        errorlist.add(
                                new CompilerError(
                                   IDecisionTableError.Type.TABLE,
                                   "Condition Table Compile Error ",
                                   i,col
                                )
                        );
                        return;
          }
          last     = here;
          laststep = yes;
        }
            }   
      if(laststep){                    // Once we have traced the column, add the actions.
        last.iftrue=ANode.newANode(this,col)
      }else{
        last.iffalse=ANode.newANode(this,col);
      }
           
    }
        DTNode.Coordinate rowCol = decisiontree.validate();
        if(rowCol!=null){
            errorlist.add(
               new CompilerError(IDecisionTableError.Type.TABLE,"Condition Table isn't balanced.",rowCol.row,rowCol.col)
            );       
            compiled = false;
        }
  }
 
    boolean newline=true;
   
    private void printattrib(PrintStream p, String tag, String body){
        if(!newline){p.println();}
        p.print("<"); p.print(tag); p.print(">");
        p.print(body);
        p.print("</"); p.print(tag); p.print(">");
        newline = false;
    }
   
    private void openTag(PrintStream p,String tag){
        if(!newline){p.println();}
        p.print("<"); p.print(tag); p.print(">");
        newline=false;
    }
   
    /**
     * Write the XML representation of this decision table to the given outputstream.
     * @param o Output stream where the XML for this decision table will be written.
     */
    public void writeXML(PrintStream p){
        p.println("<decision_table>");
        newline = true;
        printattrib(p,"table_name",dtname.stringValue());
        Iterator<RName> ifields = fields.keySet().iterator();
        while(ifields.hasNext()){
            RName name = ifields.next();
            printattrib(p,name.stringValue(),fields.get(name));
        }
        openTag(p, "conditions");
        for(int i=0; i< conditions.length; i++){
            openTag(p, "condition_details");
            printattrib(p,"condition_number",(i+1)+"");
            printattrib(p,"condition_description",GenericXMLParser.encode(conditions[i]));
            printattrib(p,"condition_postfix",GenericXMLParser.encode(conditionsPostfix[i]));
            printattrib(p,"condition_comment",GenericXMLParser.encode(conditionsComment[i]));
            p.println();
            newline=true;
            for(int j=0; j<maxcol; j++){
               p.println("<condition_column column_number=\""+(j+1)+"\" column_value=\""+conditiontable[i][j]+"\" />");
            }
            p.println("</condition_details>");
        }
        p.println("</conditions>");
        openTag(p, "actions");
        for(int i=0; i< actions.length; i++){
            openTag(p, "action_details");
            printattrib(p,"action_number",(i+1)+"");
            printattrib(p,"action_description",GenericXMLParser.encode(actions[i]));
            printattrib(p,"action_postfix",GenericXMLParser.encode(actionsPostfix[i]));
            printattrib(p,"action_comment",GenericXMLParser.encode(actionsComment[i]));
            p.println();
            newline=true;
            for(int j=0; j<maxcol; j++){
               if(actiontable[i][j].length()>0){
                   p.println("<action_column column_number=\""+(j+1)+"\" column_value=\""+actiontable[i][j]+"\" />");
               }
            }
            p.println("</action_details>");
        }
        p.println("</actions>");
        p.println("</decision_table>");
    }
   
   
  /**
   * All Decision Tables are executable.
   */
  public boolean isExecutable() {
    return true;
  }
   
  /**
   * The string value of the decision table is simply its name.
   */
  public String stringValue() {
        String number = fields.get("ipad_id");
        if(number==null)number = "";
    return number+" "+dtname.stringValue();
  }
 
  /**
   * The string value of the decision table is simply its name.
   */
  public String toString() {
    return stringValue();
  }
   
  /**
     * Return the postFix value
   */
    public String postFix() {
        return dtname.stringValue();
    }

    /**
   * The type is Decision Table.
   */
  public RType type() {
    return dttype;
  }

  /**
   * @return the actions
   */
  public String[] getActions() {
    return actions;
  }
 
  /**
   * @return the actiontable
   */
  public String[][] getActiontable() {
    return actiontable;
  }

  /**
   * @return the conditions
   */
  public String[] getConditions() {
    return conditions;
  }

  /**
   * @return the conditiontable
   */
  public String[][] getConditiontable() {
    return conditiontable;
  }
 
  public String getDecisionTableId(){
    return fields.get(RName.getRName("table_number"));
  }
 
  public void setDecisionTableId(String decisionTableId){
    fields.put(RName.getRName("table_number"),decisionTableId);
  }
 
  public String getPurpose(){
    return fields.get(RName.getRName("purpose"));
  }
 
  public void setPurpose(String purpose){
    fields.put(RName.getRName("purpose"),purpose);
  }
 
  public String getComments(){
    return fields.get(RName.getRName("comments"));
  }
 
  public void setComments(String comments){
    fields.put(RName.getRName("comments"),comments);
  }
 
  public String getReference(){
    return fields.get(RName.getRName("policy_reference"));
  }
 
  public void setReference(String reference){
    fields.put(RName.getRName("policy_reference"),reference);
  }

  /**
   * @return the dtname
   */
  public String getDtname() {
    return dtname.stringValue();
  }


  /**
   * @return the ractions
   */
  public IRObject[] getRactions() {
    return ractions;
}
  /**
   * @param ractions the ractions to set
   */
  public void setRactions(IRObject[] ractions) {
    this.ractions = ractions;
  }

  /**
   * @return the rconditions
   */
  public IRObject[] getRconditions() {
    return rconditions;
  }

  /**
   * @param rconditions the rconditions to set
   */
  public void setRconditions(IRObject[] rconditions) {
    this.rconditions = rconditions;
  }

  /**
   * @param actions the actions to set
   */
  public void setActions(String[] actions) {
    this.actions = actions;
  }

  /**
   * @param actiontable the actiontable to set
   */
  public void setActiontable(String[][] actiontable) {
    this.actiontable = actiontable;
  }

  /**
   * @param conditions the conditions to set
   */
  public void setConditions(String[] conditions) {
    this.conditions = conditions;
  }

  /**
   * @param conditiontable the conditiontable to set
   */
  public void setConditiontable(String[][] conditiontable) {
    this.conditiontable = conditiontable;
  }


  /**
   * @return the actionsComment
   */
  public final String[] getActionsComment() {
    return actionsComment;
  }


  /**
   * @param actionsComment the actionsComment to set
   */
  public final void setActionsComment(String[] actionsComment) {
    this.actionsComment = actionsComment;
  }


  /**
   * @return the actionsPostfix
   */
  public final String[] getActionsPostfix() {
    return actionsPostfix;
  }


  /**
   * @param actionsPostfix the actionsPostfix to set
   */
  public final void setActionsPostfix(String[] actionsPostfix) {
    this.actionsPostfix = actionsPostfix;
  }


  /**
   * @return the conditionsComment
   */
  public final String[] getConditionsComment() {
    return conditionsComment;
  }


  /**
   * @param conditionsComment the conditionsComment to set
   */
  public final void setConditionsComment(String[] conditionsComment) {
    this.conditionsComment = conditionsComment;
  }


  /**
   * @return the conditionsPostfix
   */
  public final String[] getConditionsPostfix() {
    return conditionsPostfix;
  }


  /**
   * @param conditionsPostfix the conditionsPostfix to set
   */
  public final void setConditionsPostfix(String[] conditionsPostfix) {
    this.conditionsPostfix = conditionsPostfix;
  }

    /**
     * A little helpper function that inserts a new column in a table
     * of strings organized as String table [row][column];  Inserts blanks
     * in all new entries, so this works for both conditions and actions.
     * @param table    
     * @param col
     */
    private static void insert(String[][]table, int maxcol, final int col){
        for(int i=0; i<maxcol; i++){
            for(int j=15; j> col; j--){
                table[i][j]=table[i][j-1];
            }
            table[i][col]=" ";
        }  
    }
  /**
     * Insert a new column at the given column number (Zero based)
     * @param col The zero based column number for the new column
     * @throws RulesException
   */
    public void insert(int col) throws RulesException {
        if(maxcol>=16){
            throw new RulesException("TableTooBig","insert","Attempt to insert more than 16 columns in a Decision Table");
        }
        insert(conditiontable,maxcol,col);
        insert(actiontable,maxcol,col);
    }
   
    /**
     * Balances an unbalanced decision table.  The additional columns have
     * no actions added.  There are two approaches to balancing tables.  One
     * is to have executed all columns whose conditions are met.  The other is
     * to execute only the first column whose conditions are met.  This
     * routine executes all columns whose conditions are met.
     */
    public void buildUnbalanced(DTState state, UnbalancedType type) {
       if(
           conditiontable.length == 0 ||
         conditiontable[0].length == 0 ||           // If we have no conditions, or
           conditiontable[0][0].equals("*")){         // If *, we just execute all actions
         decisiontree = ANode.newANode(this,0);    //   checked in the first column            
         return;
      
      
       if(conditions.length<1){ 
           errorlist.add(
                   new CompilerError(
                           IDecisionTableError.Type.CONDITION,
                           "You have to have at least one condition in a decision table",
                           0,0)
           );       
       }

       /**
        *
        */
       CNode top = new CNode(this,1,0,this.rconditions[0]);
    
       for(int col=0;col<maxcol;col++){                             // Look at each column.
           boolean nonemptycolumn = false;
           for(int row=0; row<conditions.length; row++){
               String v      = conditiontable[row][col];                     // Get the value from the condition table
               if(v.equals(DASH) || v.equals(" ")){
                   v = DASH;
                   conditiontable[row][col]=DASH;
               }else{
                   nonemptycolumn = true;
               }
           }
           if(nonemptycolumn){   
             try {                         
                int numerrs = errorlist.size()+1;                   // Ignore all but the first error in a column
                processCol(type,top,0,col,-1);                      // Process a column.
                while(errorlist.size()>numerrs){                    // Throw away all but one.
                    errorlist.remove(errorlist.size()-1);
                }
      
             } catch (Exception e) {
                /** Any error detected is recorded in the errorlist.  Nothing to do here **/                                           
             }
           }
       }
       ANode defaults;
       defaults = new ANode(this);
       addDefaults(top,defaults);                                   // Add defaults to all unmapped branches
       decisiontree = optimize(state, top);                                // Optimize the given tree.
    }    

    /**
     * Replace any untouched branches in the tree with a pointer
     * to the defaults for this table.  We only replace nulls.
     * @param node
     * @param defaults
     * @return
     */
    private DTNode addDefaults(DTNode node, ANode defaults){
        if(node == null ) return defaults;
        if(node instanceof ANode)return node;
        CNode cnode = (CNode)node;
        cnode.iffalse = addDefaults(cnode.iffalse,defaults);
        cnode.iftrue  = addDefaults(cnode.iftrue, defaults);
        return node;
    }
        
    private void addAll(DTNode node, ANode all){
       if(node.getClass()==ANode.class){
           ((ANode)node).addNode(all);
       }else{
           addAll(  ((CNode)node).iffalse ,all);
           addAll(  ((CNode)node).iftrue  ,all);
       }   
    }
   
    /**
     * Replaces the given DTNode with the optimized DTNode.
     * @param node
     * @return
     */
    private DTNode optimize(DTState state, DTNode node){
        ANode opt = node.getCommonANode(state);
        if(opt!=null){
            return opt;
        }
        CNode cnode = (CNode) node;
        cnode.iftrue  = optimize(state, cnode.iftrue);
        cnode.iffalse = optimize(state, cnode.iffalse);
        if(cnode.iftrue.equalsNode(state, cnode.iffalse)){
            cnode.iftrue.addNode(cnode.iffalse);
            return cnode.iftrue;
        }
        return cnode;
    }
   
    /**
     * Build a path through the decision tables for a particular column.
     * This routine throws an exception, but the calling routine just ignores it.
     * That way we don't flood the error list with lots of duplicate errors.
     * @param here
     * @param row
     * @param col
     * @param star -- Have we encountered a star as a column value yet.
     * @return
     */
    private DTNode processCol(UnbalancedType code, DTNode here, int row, int col, int istar) throws Exception{
       
        if (starColumn >=0 && col>starColumn){  
            int rowX = 0;
           
            for(;conditiontable[rowX][col] == DASH;rowX++);
           
            errorlist.add(
                    new CompilerError (
                         IDecisionTableError.Type.TABLE,
                         "Only one 'star' column is allowed, and it must be the last column.","*",rowX));
        }
       
        if(row >= conditions.length){                           // Ah, end of the column!
           
            ANode thisCol = ANode.newANode(this, col);          // Get a new ANode for this column
            thisCol.setStar(istar>=0);
           
            // Star columns can do one of two things.  They can fill all unused paths through
            // the decision table (the "Otherwise" or "default" option), or they can fill all paths
            // (used and unused) through the decision table (the "always" option).
            if(istar>=0){
               
                starColumn = col;             // Record the star column for error checking purposes.
               
                boolean otherwise =    conditions[istar].trim().equalsIgnoreCase("otherwise")
                                    || conditions[istar].trim().equalsIgnoreCase("default");
               
                boolean always    =    conditions[istar].trim().equalsIgnoreCase("always");
               
                if (!always && !otherwise){
                    errorlist.add(
                            new CompilerError (
                                 IDecisionTableError.Type.CONDITION,
                                 "A condition row with a star ('*') must have a condition with a value"+
                                 " of 'default', 'otherwise', or 'always'","*",0));
                                
                }
               
                if(here == null){               // If here is null, then this is an unused path.
                    return thisCol;             // This will be the only path out for "Otherwise", but
                }                               // "Always" is going to cover it too.
               
                // Okay, this path is covered by some path.  We will leave it unchanged for "otherwise".
                if(otherwise){
                    otherwiseColumn = col;      // Remember which column is the "otherwise" column.
                    return here;
                }   
               
                // Okay, this path is covered by some path, so we will add to it for "always".
                if (always){
                    alwaysColumn = col;         // Remember which column is the "always" column.
                    thisCol.addNode((ANode)here);
                    return thisCol;
                }
               
            }
           
           
            if(here!=null && code == UnbalancedType.FIRST){           // If we execute only the First, we are done!
               return here;
            }

            if( here!=null && code == UnbalancedType.ALL){            // If Some path lead here, fold the
                thisCol.addNode((ANode)here);                   //    old stuff in with this column.
                return thisCol;
            }
           
            return thisCol;                                           // Return the mix!
        }

        String v      = conditiontable[row][col];                     // Get the value from the condition table
       
        boolean dcare = v == DASH;                                    // Standardize Don't cares.
        boolean yes   = v.equalsIgnoreCase("y");
        boolean no    = v.equalsIgnoreCase("n");
               
        if(istar>=0 && (yes | no )){
            errorlist.add(
               new CompilerError (
                    IDecisionTableError.Type.CONDITION,
                    "Cannot follow a '*' with a '"+v+"' at row "+(row+1)+" column "+(col+1),v,0));
        }

        if(v.equalsIgnoreCase("*")){
            istar = row;
        }
       
        if(istar<0 && !yes && !no && !dcare){
            errorlist.add(
                new CompilerError (
                        IDecisionTableError.Type.CONDITION,
                        "Bad value in Condition Table '"+v+"' at row "+(row+1)+" column "+(col+1),v,0));
        }
       
        if((here==null || here.getRow()!= row ) && dcare){            // If we are a don't care, but not on a row
            return processCol(code,here,row+1,col,istar);             //   that matches us, we skip this row for now.
        }
       
        if(istar>=0){                                                 // If a star, then return the action node 
            DTNode t = processCol(code,here,row+1,col,istar);         //   for this position.
            t.setStar(true);
            return t;
        }
       
        if(here==null){                                               // If this node is null,
            here = new CNode(this,col,row,rconditions[row]);          //   a condition node, create it!
        }else if ( here.getRow()!= row ){                             // If this is the wrong node, and I need
            CNode t = new CNode(this,col,row,rconditions[row]);       //   a condition node, create a new one and insert it.
            t.iffalse = here;                                         // Put the node I have on the false tree
            t.iftrue  = here.cloneDTNode();                           //   and its clone on the true path.
            here = t;                                                 // Continue with the new node. 
        }
       
        if(yes || dcare){                                                   // If 'y' or a don't care,
            DTNode next = ((CNode) here).iftrue;                            // Recurse on the True Branch.
            DTNode t    = processCol(code,next,row+1,col,-1);
            ((CNode) here).iftrue = t;
            if(yes && t.getStar()){
                errorlist.add(
                   new CompilerError (
                        IDecisionTableError.Type.CONDITION,
                        "Cannot follow a 'Y' with a '*' at row "+(row+1)+" column "+(col+1),v,0));
            }
        }
        if (no || dcare){                                                   // If 'n' or a don't care, 
            DTNode next = ((CNode) here).iffalse;                           // Recurse on the False branch.  Note that
            DTNode t    = processCol(code,next,row+1,col,-1);
            ((CNode) here).iffalse = t;
            if(no && t.getStar()){                                         
                errorlist.add(
                   new CompilerError (
                        IDecisionTableError.Type.CONDITION,
                        "Cannot follow a 'N' with a '*' at row "+(row+1)+" column "+(col+1),v,0));
            }
        }
       
        return here;                                                  // Return the Condition node.
    }
   
    /**
     * In the case of an unbalanced decision table, this method returns a balanced
     * decision table using one of the two unbalanced rules:  FIRST (which executes only
     * the first column whose conditions are matched) and ALL (which executes all columns
     * whose conditions are matched).  If the decision table is balanced, this method returns
     * an "optimized" decision table where all possible additional "don't cares" are inserted.
     *
     * @return
     */
    RDecisionTable balancedTable(IRSession session) throws RulesException{
        if(balanceTable==null)balanceTable = new BalanceTable(this);
        return balanceTable.balancedTable(session);
    }
   
    public BalanceTable getBalancedTable() throws RulesException {
        return new BalanceTable(this);
    }
   
    public Iterator<RDecisionTable> DecisionTablesCalled(){
        ArrayList<RDecisionTable> tables = new ArrayList<RDecisionTable>();
        ArrayList<RArray>         stack  = new ArrayList<RArray>();
        for(int i=0;i<ractions.length;i++){
           addTables(ractions[i],stack,tables);
        }
        return tables.iterator();
    }
   
    private void addTables(IRObject action,List<RArray> stack, List<RDecisionTable> tables){
        if(action==null)return;
        if(action.type().getId()==iArray){
            RArray array = (RArray)action;
            if(stack.contains(array))return;    // We have already been here.
            stack.add(array);
            try {     // As this is an array, arrayValue() will not ever throw an exception
                @SuppressWarnings({"unchecked"})
                Iterator objects = array.arrayValue().iterator();
                while(objects.hasNext()){
                    addTables((IRObject) objects.next(),stack,tables);
                }
            } catch (RulesException e) { }
        }
        if(action.type().getId()==iDecisiontable && !tables.contains(action)){
            tables.add((RDecisionTable)action);
        }
    }
    /**
     * Returns the list of Decision Tables called by this Decision Table
     * @return
     */   
    ArrayList<RDecisionTable> decisionTablesCalled(){
        ArrayList<RDecisionTable> calledTables = new ArrayList<RDecisionTable>();

        addlist(calledTables, rinitialActions);
        addlist(calledTables, rconditions);
        addlist(calledTables, ractions);
       
        return calledTables;
    }
    /**
     * We do a recursive search down each IRObject in these lists, looking for
     * references to Decision Tables.  We only add references to Decision Tables
     * to the list of called tables if the list of called tables doesn't yet have
     * that reference.
     *
     * @param calledTables
     * @param list
     */
    private void addlist(ArrayList<RDecisionTable> calledTables, IRObject [] list){
        for(int i=0; i<list.length; i++){
            ArrayList<RDecisionTable> tables = new ArrayList<RDecisionTable>();
            ArrayList<RArray>         stack  = new ArrayList<RArray>();
            getTables(stack, tables, list[i]);
            for(RDecisionTable table : tables){
                if(!calledTables.contains(table))calledTables.add(table);
            }
        }
    }
    /**
     * Here we do a recursive search of all the constructs in an IROBject.  This
     * is because some IRObjects are arrays, so we search them as well.
     * @param obj
     * @return
     */
    private ArrayList<RDecisionTable> getTables(ArrayList<RArray>stack, ArrayList<RDecisionTable> tables, IRObject obj){
       
        if(obj instanceof RDecisionTable) tables.add((RDecisionTable) obj);
        if(obj instanceof RArray && !stack.contains(obj)){
            stack.add((RArray) obj);
            for(IRObject obj2 : (RArray) obj){
                getTables(stack,tables,obj2);
            }
        }
        return tables;
    }

    public String[] getPolicystatements() {
        return policystatements;
    }

    public String[] getPolicystatementsPostfix() {
        return policystatementsPostfix;
    }

    public IRObject[] getRpolicystatements() {
        return rpolicystatements;
    }

    public DTNode getDecisiontree() {
        return decisiontree;
    }
    /**
     * This method provides field values given a field name.  A few "virtual" field names are
     * supported so as to avoid having to have special code to access these values.  They include
     * the Table_Name, File_Name, etc.
     * @param fieldname
     * @return
     */
    public String getField(String fieldname){
        RName fn = RName.getRName(fieldname);
        if(fn.equals(table_name)){
            return dtname.stringValue();
        }else if(fn.equals(type_name)){
            switch(type){
                case ALL :      return "ALL";
                case FIRST :    return "FIRST";
                case BALANCED : return "BALANCED";
                default :       return "UNDEFINED";
            }
        }
        return fields.get(fn);
    }
   
}
TOP

Related Classes of com.dtrules.decisiontables.RDecisionTable

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.