package org.openrosa.client.model; import java.io.Serializable; import java.util.Date; import org.openrosa.client.util.FormUtil; import com.google.gwt.i18n.client.DateTimeFormat; /** * A condition which is part of a rule. For definition of a rule, go to the Rule class. * E.g. If sex is Male. If age is greater than than 4. etc * *@author Daniel Kayiwa */ public class Condition implements Serializable{ /** The unique identifier of the question referenced by this condition. */ private int questionId = ModelConstants.NULL_ID; /** The operator of the condition. Eg Equal to, Greater than, etc. */ private int operator = ModelConstants.OPERATOR_NULL; /** The aggregate function. Eg Length, Value. */ private int function = ModelConstants.FUNCTION_VALUE; /** The value checked to see if the condition is true or false. * For the above example, the value would be 4 or the id of the Male option. * For a list of options this value is the option id, not the value or text value. */ private String value = ModelConstants.EMPTY_STRING; /** The the question whose value is dynamically put in the value property. * This is useful for only design mode when the question variablename changes * and hence we need to change the value or the value property. * */ private QuestionDef valueQtnDef; /** * The second value checked to see if the condition is true. This has values or * for contitions that have the OR operator or AND operator. Eg weight > 0 and weight < 100 */ private String secondValue = ModelConstants.EMPTY_STRING; /** The unique identifier of a condition. */ private int id = ModelConstants.NULL_ID; /** Creates a new condition object. */ public Condition(){ } /** Copy constructor. */ public Condition(Condition condition){ this(condition.getId(),condition.getQuestionId(),condition.getOperator(),condition.getFunction(),condition.getValue()); } /** * Creates a new condition object from its parameters. * * @param id - the numeric identifier of the condition. * @param questionId - the numeric identifier of the question. * @param operator - the condition operator. * @param value - the value to be equated to. */ public Condition(int id,int questionId, int operator, int function, String value) { this(); setQuestionId(questionId); setOperator(operator); setFunction(function); setValue(value); setId(id); } public int getOperator() { return operator; } public void setOperator(int operator) { this.operator = operator; } public int getFunction() { return function; } public void setFunction(int function) { this.function = function; } public int getQuestionId() { return questionId; } public void setQuestionId(int questionId) { this.questionId = questionId; } public String getValue() { return value; } public void setValue(String value) { this.value = value; } public int getId() { return id; } public void setId(int conditionId) { this.id = conditionId; } public String getSecondValue() { return secondValue; } public void setSecondValue(String secondValue) { this.secondValue = secondValue; } /** * Checks if this condition is true or false. * * @param formDef the form definition object. * @param validation set to true if this is a validation rule condition, else false if skip rule condition. * @return true if the condition is true, else false. */ public boolean isTrue(FormDef formDef, boolean validation){ String tempValue = value; boolean ret = true; try{ QuestionDef qn = formDef.getQuestion(this.questionId); if(qn == null) return false; //possibly question deleted if(value.startsWith(formDef.getQuestionID()+"/")){ QuestionDef qn2 = formDef.getQuestion(value.substring(value.indexOf('/')+1)); if(qn2 != null){ value = qn2.getAnswer(); if(value == null || value.trim().length() == 0){ value = tempValue; if(qn.getAnswer() == null || qn.getAnswer().trim().length() == 0) return true; //Both questions not answered yet return false; } else if(qn.getAnswer() == null || qn.getAnswer().trim().length() == 0){ value = tempValue; return false; //return validation; //TODO Do we really need validations to return true when qtn is not answered? } } } switch(qn.getDataType()){ case QuestionDef.QTN_TYPE_TEXT: ret = isTextTrue(qn,validation); break; case QuestionDef.QTN_TYPE_REPEAT: case QuestionDef.QTN_TYPE_NUMERIC: ret = isNumericTrue(qn,validation); break; case QuestionDef.QTN_TYPE_DATE: ret = isDateTrue(qn,validation); break; case QuestionDef.QTN_TYPE_DATE_TIME: ret = isDateTimeTrue(qn,validation); break; case QuestionDef.QTN_TYPE_DECIMAL: ret = isDecimalTrue(qn,validation); break; case QuestionDef.QTN_TYPE_LONG: ret = isLongTrue(qn,validation); break; case QuestionDef.QTN_TYPE_LIST_EXCLUSIVE: case QuestionDef.QTN_TYPE_LIST_EXCLUSIVE_DYNAMIC: ret = isListExclusiveTrue(qn,validation); break; case QuestionDef.QTN_TYPE_LIST_MULTIPLE: ret = isListMultipleTrue(qn,validation); break; case QuestionDef.QTN_TYPE_TIME: ret = isTimeTrue(qn,validation); break; case QuestionDef.QTN_TYPE_BOOLEAN: ret = isTextTrue(qn,validation); break; } } catch(Exception ex){ ex.printStackTrace(); } value = tempValue; return ret; } /** * Check to see if a condition, attached to a numeric question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isNumericTrue(QuestionDef qtn, boolean validation){ //return value.equals(qtn.getAnswer()); try{ if(qtn.getAnswer() == null || qtn.getAnswer().trim().length() == 0){ if(validation && operator == ModelConstants.OPERATOR_IS_NOT_NULL) return false; else if(validation || operator == ModelConstants.OPERATOR_NOT_EQUAL || operator == ModelConstants.OPERATOR_NOT_BETWEEN) return true; return operator == ModelConstants.OPERATOR_IS_NULL; } else if(operator == ModelConstants.OPERATOR_IS_NOT_NULL) return true; long answer = Long.parseLong(qtn.getAnswer()); long longValue = Long.parseLong(value); long secondLongValue = longValue; if(secondValue != null && secondValue.trim().length() > 0) secondLongValue = Long.parseLong(secondValue); if(operator == ModelConstants.OPERATOR_EQUAL) return longValue == answer; else if(operator == ModelConstants.OPERATOR_NOT_EQUAL) return longValue != answer; else if(operator == ModelConstants.OPERATOR_LESS) return answer < longValue; else if(operator == ModelConstants.OPERATOR_LESS_EQUAL) return answer < longValue || longValue == answer; else if(operator == ModelConstants.OPERATOR_GREATER) return answer > longValue; else if(operator == ModelConstants.OPERATOR_GREATER_EQUAL) return answer > longValue || longValue == answer; else if(operator == ModelConstants.OPERATOR_BETWEEN) return answer > longValue && longValue < secondLongValue; else if(operator == ModelConstants.OPERATOR_NOT_BETWEEN) return !(answer > longValue && longValue < secondLongValue); } catch(Exception ex){ ex.printStackTrace(); } return false; } //TODO Should this test be case sensitive? /** * Check to see if a condition, attached to a text question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isTextTrue(QuestionDef qtn, boolean validation){ String answer = qtn.getAnswer(); if(function == ModelConstants.FUNCTION_VALUE){ if(answer == null || answer.trim().length() == 0){ if(validation && operator == ModelConstants.OPERATOR_IS_NOT_NULL) return false; else if(validation || operator == ModelConstants.OPERATOR_NOT_EQUAL || operator == ModelConstants.OPERATOR_NOT_START_WITH || operator == ModelConstants.OPERATOR_NOT_CONTAIN) return true; return operator == ModelConstants.OPERATOR_IS_NULL; } else if(operator == ModelConstants.OPERATOR_IS_NOT_NULL) return true; if(operator == ModelConstants.OPERATOR_EQUAL) return value.equals(qtn.getAnswer()); else if(operator == ModelConstants.OPERATOR_NOT_EQUAL) return !value.equals(qtn.getAnswer()); else if(operator == ModelConstants.OPERATOR_STARTS_WITH) return answer.startsWith(value); else if(operator == ModelConstants.OPERATOR_NOT_START_WITH) return !answer.startsWith(value); else if(operator == ModelConstants.OPERATOR_CONTAINS) return answer.contains(value); else if(operator == ModelConstants.OPERATOR_NOT_CONTAIN) return !answer.contains(value); } else{ if(answer == null || answer.trim().length() == 0) return true; long len1 = 0, len2 = 0, len = 0; if(value != null && value.trim().length() > 0) len1 = Long.parseLong(value); if(secondValue != null && secondValue.trim().length() > 0) len2 = Long.parseLong(secondValue); len = answer.trim().length(); if(operator == ModelConstants.OPERATOR_EQUAL) return len == len1; else if(operator == ModelConstants.OPERATOR_NOT_EQUAL) return len != len1; else if(operator == ModelConstants.OPERATOR_LESS) return len < len1; else if(operator == ModelConstants.OPERATOR_LESS_EQUAL) return len <= len1; else if(operator == ModelConstants.OPERATOR_GREATER) return len > len1; else if(operator == ModelConstants.OPERATOR_GREATER_EQUAL) return len >= len1; else if(operator == ModelConstants.OPERATOR_BETWEEN) return len > len1 && len < len2; else if(operator == ModelConstants.OPERATOR_NOT_BETWEEN) return !(len > len1 && len < len2); } return false; } /** * Check to see if a condition, attached to a date question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isDateTrue(QuestionDef qtn, boolean validation){ //return value.equals(qtn.getAnswer()); try{ if(qtn.getAnswer() == null || qtn.getAnswer().trim().length() == 0){ if(validation && operator == ModelConstants.OPERATOR_IS_NOT_NULL) return false; else if(validation || operator == ModelConstants.OPERATOR_NOT_EQUAL || operator == ModelConstants.OPERATOR_NOT_BETWEEN) return true; return operator == ModelConstants.OPERATOR_IS_NULL; } else if(operator == ModelConstants.OPERATOR_IS_NOT_NULL) return true; Date answer = getDateTimeSubmitFormat(qtn).parse(qtn.getAnswer()); Date dateValue = null; if(QuestionDef.isDateFunction(value)) dateValue = QuestionDef.getDateFunctionValue(value); else dateValue = getDateTimeSubmitFormat(qtn).parse(value); Date secondDateValue = dateValue; if(secondValue != null && secondValue.trim().length() > 0){ if(QuestionDef.isDateFunction(secondValue)) secondDateValue = QuestionDef.getDateFunctionValue(secondValue); else secondDateValue = getDateTimeSubmitFormat(qtn).parse(secondValue); } if(operator == ModelConstants.OPERATOR_EQUAL) return dateValue.equals(answer); else if(operator == ModelConstants.OPERATOR_NOT_EQUAL) return !dateValue.equals(answer); else if(operator == ModelConstants.OPERATOR_LESS) return answer.before(dateValue); else if(operator == ModelConstants.OPERATOR_LESS_EQUAL) return answer.before(dateValue) || dateValue.equals(answer); else if(operator == ModelConstants.OPERATOR_GREATER) return answer.after(dateValue); else if(operator == ModelConstants.OPERATOR_GREATER_EQUAL) return answer.after(dateValue) || dateValue.equals(answer); else if(operator == ModelConstants.OPERATOR_BETWEEN) return answer.after(dateValue) && dateValue.before(secondDateValue); else if(operator == ModelConstants.OPERATOR_NOT_BETWEEN) return !(answer.after(dateValue) && dateValue.before(secondDateValue)); } catch(Exception ex){ ex.printStackTrace(); } return false; } private DateTimeFormat getDateTimeSubmitFormat(QuestionDef qtn){ if(qtn.getDataType() == QuestionDef.QTN_TYPE_DATE_TIME) return FormUtil.getDateTimeSubmitFormat(); else return FormUtil.getDateSubmitFormat(); } /** * Check to see if a condition, attached to a date and time question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isDateTimeTrue(QuestionDef qtn, boolean validation){ return isDateTrue(qtn,validation); } /** * Check to see if a condition, attached to a time question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isTimeTrue(QuestionDef qtn, boolean validation){ return isDateTrue(qtn,validation); } /** * Check to see if a condition, attached to a multiple select question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isListMultipleTrue(QuestionDef qtn, boolean validation){ //if(qtn.answerContainsValue(value)) // return true; //return value.equals(qtn.getAnswer()); try{ if(qtn.getAnswer() == null || qtn.getAnswer().trim().length() == 0){ if(validation && operator == ModelConstants.OPERATOR_IS_NOT_NULL) return false; else if(validation || operator == ModelConstants.OPERATOR_NOT_EQUAL || operator == ModelConstants.OPERATOR_NOT_IN_LIST) return true; return operator == ModelConstants.OPERATOR_IS_NULL; } else if(operator == ModelConstants.OPERATOR_IS_NOT_NULL) return true; //return qtn.getAnswer().contains(value); switch(operator){ case ModelConstants.OPERATOR_EQUAL: return qtn.getAnswer().contains(value); //qtn.getAnswer().equals(value); case ModelConstants.OPERATOR_NOT_EQUAL: return !qtn.getAnswer().contains(value); //!qtn.getAnswer().equals(value); case ModelConstants.OPERATOR_IN_LIST: return value.contains(qtn.getAnswer()); case ModelConstants.OPERATOR_NOT_IN_LIST: return !value.contains(qtn.getAnswer()); default: return false; } } catch(Exception ex){ ex.printStackTrace(); } return false; } /** * Check to see if a condition, attached to a single select question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isListExclusiveTrue(QuestionDef qtn, boolean validation){ try{ if(qtn.getAnswer() == null || qtn.getAnswer().trim().length() == 0){ //return operator != PurcConstants.OPERATOR_EQUAL; if(validation && operator == ModelConstants.OPERATOR_IS_NOT_NULL) return false; else if(validation || operator == ModelConstants.OPERATOR_NOT_EQUAL || operator == ModelConstants.OPERATOR_NOT_IN_LIST) return true; return operator == ModelConstants.OPERATOR_IS_NULL; } else if(operator == ModelConstants.OPERATOR_IS_NOT_NULL) return true; switch(operator){ case ModelConstants.OPERATOR_EQUAL: return qtn.getAnswer().equals(value); case ModelConstants.OPERATOR_NOT_EQUAL: return !qtn.getAnswer().equals(value); case ModelConstants.OPERATOR_IN_LIST: return value.contains(qtn.getAnswer()); case ModelConstants.OPERATOR_NOT_IN_LIST: return !value.contains(qtn.getAnswer()); default: return false; } } catch(Exception ex){ ex.printStackTrace(); } return false; } /** * Check to see if a condition, attached to a decimal question, is true. * * @param qtn the question whose answer we are using to test the condition * @param validation has value of true if this is a validation logic condition, else false if skip logic one. * @return true if the condition is true, else false. */ private boolean isDecimalTrue(QuestionDef qtn, boolean validation){ //return value.equals(qtn.getAnswer()); try{ if(qtn.getAnswer() == null || qtn.getAnswer().trim().length() == 0){ if(validation && operator == ModelConstants.OPERATOR_IS_NOT_NULL) return false; else if(validation || operator == ModelConstants.OPERATOR_NOT_EQUAL || operator == ModelConstants.OPERATOR_NOT_BETWEEN) return true; return operator == ModelConstants.OPERATOR_IS_NULL; } else if(operator == ModelConstants.OPERATOR_IS_NOT_NULL) return true; double answer = Double.parseDouble(qtn.getAnswer()); double doubleValue = Double.parseDouble(value); double secondDoubleValue = doubleValue; if(secondValue != null && secondValue.trim().length() > 0) secondDoubleValue = Double.parseDouble(secondValue); if(operator == ModelConstants.OPERATOR_EQUAL) return doubleValue == answer; else if(operator == ModelConstants.OPERATOR_NOT_EQUAL) return doubleValue != answer; else if(operator == ModelConstants.OPERATOR_LESS) return answer < doubleValue; else if(operator == ModelConstants.OPERATOR_LESS_EQUAL) return answer < doubleValue || doubleValue == answer; else if(operator == ModelConstants.OPERATOR_GREATER) return answer > doubleValue; else if(operator == ModelConstants.OPERATOR_GREATER_EQUAL) return answer > doubleValue || doubleValue == answer; else if(operator == ModelConstants.OPERATOR_BETWEEN) return answer > doubleValue && doubleValue < secondDoubleValue; else if(operator == ModelConstants.OPERATOR_NOT_BETWEEN) return !(answer > doubleValue && doubleValue < secondDoubleValue); } catch(Exception ex){ ex.printStackTrace(); } return false; } public boolean isLongTrue(QuestionDef qtn, boolean validation){ return true; } /** * Gets the value for this condition. If the value references another question, * it returns the answer of that question. * * @param formDef the form definition object that this condition belongs to. * @return the text value. */ public String getValue(FormDef formDef){ if(value.startsWith(formDef.getQuestionID()+"/")){ QuestionDef qn = formDef.getQuestion(value.substring(value.indexOf('/')+1)); if(qn != null) return qn.getAnswer(); } return value; } /** * Sets the new value of the condition. * * @param origValue the original value. * @param newValue the new value. */ public void updateValue(String origValue, String newValue){ if(origValue.equals(value)) value = newValue; } public void setValueQtnDef(QuestionDef valueQtnDef){ this.valueQtnDef = valueQtnDef; } public QuestionDef getValueQtnDef(){ return valueQtnDef; } /** * Checks if this condition references an answer of a particular question. * * @param questionDef the question whose answer is referenced. * @param formDef the form being filled. * @return true if it does, else false. */ public boolean hasQuestion(QuestionDef questionDef, FormDef formDef){ if(value.startsWith(formDef.getQuestionID()+"/")){ QuestionDef qtn = formDef.getQuestion(value.substring(value.indexOf('/')+1)); if(qtn != null && qtn == questionDef) return true; } return false; } /** * Gets the question, if any, referenced by a condition value. * Such values exit for cross field validations where a condition's * value is not static but dynamic in the sense that it comes from * the answer of some other question (The question we are returning) * An example of such a condition is "Male number of kids should be * less than or equal to the Total number of kids." * * @param formDef the form that this condition belongs to. * @return the question if any is found. */ public QuestionDef getQuestion(FormDef formDef){ if(value.startsWith(formDef.getQuestionID()+"/")) return formDef.getQuestion(value.substring(value.indexOf('/')+1)); return null; } }