package net.sf.egonet.persistence; import java.util.ArrayList; import net.sf.egonet.model.Alter; import net.sf.egonet.model.Question; /** * base class for a (very rudimentary) comparison ability to be used * within egoweb <IF /> tags. In this version the type and function * of a node is governed by its NodeType, but this mechanism can be changed * to provide derived classes for each type. * @author Kevin * */ public class ExpressionNode { public static enum NodeType { MATH, COMPARISON, VAR, LITERAL }; public static enum MathOp {ADD, SUB, MUL, DIV}; public static enum CompareOp { LESS_EQ, GREATER_EQ, LESS, GREATER, EQUALS, NOT_EQUALS }; public static final String CompareOpStr[] = {"<=", ">=", "<", ">", "==", "!="}; private NodeType nodeType; private MathOp mathOp; private CompareOp compareOp; private int intResult; // boolean nodes use this with 0 or 1 private int leftValue; private int rightValue; private String varName; // ( or literal in string format ) private ExpressionNode leftChild; private ExpressionNode rightChild; /** * constructor * @param compareOp - indicates the type of comparison this node * will make */ public ExpressionNode ( CompareOp compareOp) { nodeType = NodeType.COMPARISON; this.compareOp = compareOp; varName = CompareOpStr[this.compareOp.ordinal()]; leftChild = null; rightChild = null; } /** * constructor * this is the more common constructor. A simple comparison expression * such as Q1<=5 will first be parsed into individual symbols, * "Q1", "<=", "5" and then those individual strings will be passed * to this constructor. The type of node will be determined from the * nature of the string, we will assume question titles are never valid * integers or comparison operators. * @param str the basis for this node */ public ExpressionNode ( String str ) { int iCompIndex; nodeType = NodeType.LITERAL; try { intResult = Integer.parseInt(str); } catch ( NumberFormatException nfe ) { iCompIndex = compareOpStrIndex(str); if ( iCompIndex<0 ) { nodeType = NodeType.VAR; } else { nodeType = NodeType.COMPARISON; switch ( iCompIndex ) { case 0: compareOp = CompareOp.LESS_EQ; break; case 1: compareOp = CompareOp.GREATER_EQ; break; case 2: compareOp = CompareOp.LESS; break; case 3: compareOp = CompareOp.GREATER; break; case 4: compareOp = CompareOp.EQUALS; break; case 5: compareOp = CompareOp.NOT_EQUALS; break; } } } varName = str; leftChild = null; rightChild = null; } /** * used to construct trees of nodes * assumes this node is an operator, not a variable or a literal * @param node ExpressionNode to be left child */ public void setLeftChild ( ExpressionNode node) { leftChild = node; } /** * used to construct trees of nodes * assumes this node is an operator, not a variable or a literal * @param node ExpressionNode to be right child */ public void setRightChild ( ExpressionNode node) { rightChild = node; } /** * useful to display what this expression looks like * (and, if bShowVarNames is true, should duplicate the original * text string used to create the expression except for whitespace) * @param bShowVarNames if true, show variable names, if false their values * @return string representation of the expression */ public String toString(boolean bShowVarNames) { String str; str = (leftChild==null) ? "" : leftChild.toString(bShowVarNames); if ( bShowVarNames || nodeType==NodeType.MATH || nodeType==NodeType.COMPARISON ) str += varName; else str += " " + intResult + " "; if ( rightChild!=null ) str += rightChild.toString(bShowVarNames); return(str); } /** * default toString that shows variable names */ public String toString() { return(toString(true)); } /** * evaluates a tree of expression nodes. For now it returns 1 to indicate true * and 0 to indicate false * @param interviewId - needed to retrieve variables that are question answers * @param iType - needed to retrieve variables that are question answers * @param studyId - needed to retrieve variables that are question answers * @param listOfAlters - needed to retrieve variables that are question answers * @return the value of this node after all child nodes are also evaluated */ public int evaluate( Long interviewId, Question.QuestionType iType, Long studyId, ArrayList<Alter> listOfAlters) { String strQuestionVariable; leftValue = rightValue = 0; if ( leftChild!=null ) leftValue = leftChild.evaluate( interviewId, iType, studyId, listOfAlters); if ( rightChild!=null ) rightValue = rightChild.evaluate( interviewId, iType, studyId, listOfAlters); switch ( nodeType ) { case MATH: // TODO !!! break; case COMPARISON: switch ( compareOp ) { case LESS_EQ: intResult = (leftValue<=rightValue)?1:0; break; case GREATER_EQ: intResult = (leftValue>=rightValue)?1:0; break; case LESS: intResult = (leftValue<rightValue)?1:0; break; case GREATER: intResult = (leftValue>rightValue)?1:0; break; case EQUALS: intResult = (leftValue==rightValue)?1:0; break; case NOT_EQUALS: intResult = (leftValue!=rightValue)?1:0; break; } break; case VAR: strQuestionVariable = TextInsertionUtil.answerToQuestion ( varName, interviewId, iType, studyId, listOfAlters ); if ( strQuestionVariable==null || strQuestionVariable.length()==0 ) { System.out.println ( "ERROR math variable " + varName + " not found"); intResult = Integer.MAX_VALUE; break; } try { intResult = Integer.parseInt(strQuestionVariable); } catch ( NumberFormatException nfe ) { System.out.println ( "ERROR In ExpressionNode.evaluate strQuestionVariable was NOT an integer"); System.out.println ( "ERROR strQuestionVariable =" + strQuestionVariable + " variable name=" + varName); intResult = Integer.MAX_VALUE; break; } break; case LITERAL: // intResult is already set break; } return(intResult); } /** * scans a string to find the index of the next comparison operator * within it - "<", "<=", ">", ">=", "==", "!=". * Does some special logic in to give precedence to <= and >=, * returns MAX_VALUE if NONE of them are found. * This function is ad-hoc to parseComparisionList * @param str string to examine * @return index of next comparison string */ public static int indexOfNextComparisonOp ( String str ) { int indexes[]; int ix; int index = Integer.MAX_VALUE; indexes = new int[ExpressionNode.CompareOpStr.length]; for ( ix=0 ; ix<CompareOpStr.length ; ++ix ) { indexes[ix] = str.indexOf(CompareOpStr[ix]); if ( indexes[ix]<0 ) indexes[ix] = Integer.MAX_VALUE; } if ( indexes[CompareOp.LESS.ordinal()] == indexes[CompareOp.LESS_EQ.ordinal()]) indexes[CompareOp.LESS.ordinal()] = Integer.MAX_VALUE; if ( indexes[CompareOp.GREATER.ordinal()] == indexes[CompareOp.GREATER_EQ.ordinal()]) indexes[CompareOp.GREATER.ordinal()] = Integer.MAX_VALUE; for ( ix=0 ; ix<CompareOpStr.length ; ++ix ) { if ( indexes[ix]<index ) index = indexes[ix]; } return(index); } /** * a help function for parsing expression strings into * string lists and then expression trees. * @param str string to compare * @return an int indicating which comparison string str is, * -1 if it is not one */ public static int compareOpStrIndex (String str) { int ix; for ( ix=0 ; ix<CompareOpStr.length ; ++ix ) { if ( str.equals(CompareOpStr[ix])) return(ix); } return(-1); } /** * similar to compareOpStrIndex, but this just checks to * see if a string starts with a comparison operator, and if * so which one. * This is a helper function for parsing strings into string arrays * and then into ExpressionNode trees. * @param str String to examine * @return */ public static int typeOfNextCompareOp (String str) { int ix; for ( ix=0 ; ix<CompareOpStr.length ; ++ix ) { if ( str.startsWith(CompareOpStr[ix])) return(ix); } return(-1); } }