package org.openrosa.client.model;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Vector;
import org.openrosa.client.xforms.ConstraintBuilder;
import org.openrosa.client.util.FormUtil;
import org.openrosa.client.xforms.XformConstants;
import com.google.gwt.xml.client.Element;
/**
* This class represents a validation rule which in xforms is a constraint attribute.
* An example of such would be constraint=". >= 0 and . <= 150" where a value is
* required to be between 0 and 150.
*
* @author daniel
*
*/
public class ValidationRule implements Serializable{
/** The unique identifier of the question referenced by this validation rule. */
private int questionId = ModelConstants.NULL_ID;
/** A list of conditions (Condition object) to be tested for a rule.
* E.g. age is greater than 4. etc
*/
private Vector conditions;
/** The validation rule name. */
private String errorMessage;
/** Operator for combining more than one condition. (And, Or) only these two for now. */
private int conditionsOperator = ModelConstants.CONDITIONS_OPERATOR_NULL;
/** The form to which the validation rule belongs. */
private FormDef formDef;
private String itextId;
/** Constructs a rule object ready to be initialized. */
public ValidationRule(FormDef formDef){
this.formDef = formDef;
}
public ValidationRule(int questionId, FormDef formDef){
this.questionId = questionId;
this.formDef = formDef;
}
/** Copy constructor. */
public ValidationRule(ValidationRule validationRule){
setQuestionId(validationRule.getQuestionId());
setErrorMessage(validationRule.getErrorMessage());
setConditionsOperator(validationRule.getConditionsOperator());
copyConditions(validationRule.getConditions());
setFormDef(new FormDef(validationRule.getFormDef(),false));
}
/** Construct a Rule object from parameters.
*
* @param ruleId
* @param conditions
* @param action
* @param actionTargets
*/
public ValidationRule(int questionId, Vector conditions , String errorMessage) {
setQuestionId(questionId);
setConditions(conditions);
setErrorMessage(errorMessage);
}
public Vector getConditions() {
if(conditions == null){
conditions = new Vector();
}
return conditions;
}
public void setConditions(Vector conditions) {
this.conditions = conditions;
}
public int getQuestionId() {
return questionId;
}
public void setQuestionId(int questionId) {
this.questionId = questionId;
}
public int getConditionsOperator() {
return conditionsOperator;
}
public void setConditionsOperator(int conditionsOperator) {
this.conditionsOperator = conditionsOperator;
}
public Condition getConditionAt(int index) {
if(conditions == null)
return null;
return (Condition)conditions.elementAt(index);
}
public int getConditionCount() {
if(conditions == null)
return 0;
return conditions.size();
}
public String getErrorMessage() {
return errorMessage;
}
public void setErrorMessage(String errorMessage) {
this.errorMessage = errorMessage;
}
public String getItextId() {
return itextId;
}
public void setItextId(String itextId) {
this.itextId = itextId;
}
public FormDef getFormDef() {
return formDef;
}
public void setFormDef(FormDef formDef) {
this.formDef = formDef;
}
public void addCondition(Condition condition){
if(conditions == null)
conditions = new Vector();
conditions.add(condition);
}
public boolean containsCondition(Condition condition){
if(conditions == null)
return false;
return conditions.contains(condition);
}
public void updateCondition(Condition condition){
for(int i=0; i<conditions.size(); i++){
Condition cond = (Condition)conditions.elementAt(i);
if(cond.getId() == condition.getId()){
conditions.remove(i);
conditions.add(condition);
break;
}
}
}
public void removeCondition(Condition condition){
conditions.remove(condition);
}
public void removeQuestion(IFormElement questionDef){
for(int index = 0; index < getConditionCount(); index++){
Condition condition = getConditionAt(index);
if(condition.getQuestionId() == questionDef.getId() ||
(condition.getValueQtnDef() != null && condition.getValueQtnDef().getId() == questionDef.getId())){
removeCondition(condition);
index++;
}
}
}
/**
* Checks if this validation rule is valid.
*
* @return true if valid, else false.
*/
public boolean isValid(){
return isValid(formDef);
}
/**
* Checks conditions of a rule and executes the corresponding actions
*
* @param data
*/
public boolean isValid(FormDef formDef){
boolean trueFound = false, falseFound = false;
for(int i=0; i<getConditions().size(); i++){
Condition condition = (Condition)this.getConditions().elementAt(i);
if(condition.isTrue(formDef,true))
trueFound = true;
else
falseFound = true;
}
if(getConditions().size() == 1 || getConditionsOperator() == ModelConstants.CONDITIONS_OPERATOR_AND)
return !falseFound;
else if(getConditionsOperator() == ModelConstants.CONDITIONS_OPERATOR_OR)
return trueFound;
return false;
}
private void copyConditions(Vector conditions){
this.conditions = new Vector();
for(int i=0; i<conditions.size(); i++)
this.conditions.addElement(new Condition((Condition)conditions.elementAt(i)));
}
public void updateDoc(FormDef formDef){
ConstraintBuilder.fromValidationRule2Xform(this,formDef);
}
//TODO This should be smarter
public byte getMaxValue(FormDef formDef){
if(conditions == null || conditions.size() == 0)
return 127;
String value = ((Condition)conditions.get(0)).getValue(formDef);
if(value == null || value.trim().length() == 0)
return 127;
try{
return Byte.parseByte(value);
}
catch(Exception ex){}
return 127;
}
public void updateConditionValue(String origValue, String newValue){
for(int index = 0; index < this.getConditionCount(); index++)
getConditionAt(index).updateValue(origValue, newValue);
}
/**
* Creates the locale translation xpath expressions for the validation text in this rule.
*
* @param formDef the definition object to which this rule belongs.
* @param parentNode the parent node for the locale xpath expressions document.
*/
public void buildLanguageNodes(FormDef formDef, Element parentNode){
QuestionDef questionDef = formDef.getQuestion(questionId);
if(questionDef == null || questionDef.getBindNode() == null)
return;
Element node = formDef.getDoc().createElement(XformConstants.NODE_NAME_TEXT);
String xpath = FormUtil.getNodePath(questionDef.getBindNode());
xpath += "[@"+XformConstants.ATTRIBUTE_NAME_ID+"='"+ questionDef.getBindNode().getAttribute(XformConstants.ATTRIBUTE_NAME_ID)+"']";
xpath += "[@"+XformConstants.ATTRIBUTE_NAME_CONSTRAINT_MESSAGE+"]";
node.setAttribute(XformConstants.ATTRIBUTE_NAME_XPATH, xpath);
node.setAttribute(XformConstants.ATTRIBUTE_NAME_VALUE, errorMessage);
parentNode.appendChild(node);
}
public void refresh(FormDef dstFormDef, FormDef srcFormDef){
QuestionDef qtn = srcFormDef.getQuestion(questionId);
if(qtn == null)
return; //can this question really disappear?
//using variable name instead of id because id could have changed as more questions are
//added or some deleted.
QuestionDef questionDef = dstFormDef.getQuestion(qtn.getQuestionID());
if(questionDef == null)
return; //possibly question for the validation rule has been deleted.
ValidationRule validationRule = new ValidationRule(questionDef.getId(),dstFormDef);
validationRule.setConditionsOperator(getConditionsOperator());
validationRule.setErrorMessage(getErrorMessage());
for(int index = 0; index < getConditionCount(); index++){
Condition condition = getConditionAt(index);
qtn = srcFormDef.getQuestion(condition.getQuestionId());
if(qtn == null)
continue;
questionDef = dstFormDef.getQuestion(qtn.getQuestionID());
if(questionDef == null)
continue;
condition.setQuestionId(questionDef.getId());
validationRule.addCondition(new Condition(condition));
}
if(validationRule.getConditionCount() > 0)
dstFormDef.addValidationRule(validationRule);
}
/**
* Checks of a validation rule references a particular question in any of its conditions.
*
* @param questionDef the question to be referenced.
* @return true if it does, else false.
*/
public boolean hasQuestion(QuestionDef questionDef){
if(conditions == null)
return false;
for(int i=0; i<conditions.size(); i++){
Condition condition = (Condition)conditions.elementAt(i);
if(condition.hasQuestion(questionDef, formDef))
return true;
}
return false;
}
//TODO Why does the validation rule have a value of formDef different from the one passed in as parameter?
/**
* Gets the list of questions referenced by any value of any condition in this rule.
*
* @param formDef the form definition object that this rule belongs to.
* @return the list of questions.
*/
public List<QuestionDef> getValueQuestions(FormDef formDef){
if(conditions == null)
return null;
List<QuestionDef> questions = new ArrayList<QuestionDef>();
for(int i=0; i<conditions.size(); i++){
Condition condition = (Condition)conditions.elementAt(i);
QuestionDef questionDef = condition.getQuestion(formDef);
if(questionDef != null && !questions.contains(questionDef))
questions.add(questionDef);
}
return questions;
}
}