<commit_before>/*
 * Copyright (C) 2009 JavaRosa-Core Project
 *
 * 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 org.javarosa.core.model;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.javarosa.core.model.condition.Condition;
import org.javarosa.core.model.condition.Constraint;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.condition.Recalculate;
import org.javarosa.core.model.condition.Triggerable;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.data.SelectMultiData;
import org.javarosa.core.model.data.SelectOneData;
import org.javarosa.core.model.data.helper.Selection;
import org.javarosa.core.model.instance.DataModelTree;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.utils.Localizable;
import org.javarosa.core.model.utils.Localizer;
import org.javarosa.core.model.utils.QuestionPreloader;
import org.javarosa.core.services.storage.utilities.IDRecordable;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.ExtWrapList;
import org.javarosa.core.util.externalizable.ExtWrapListPoly;
import org.javarosa.core.util.externalizable.ExtWrapNullable;
import org.javarosa.core.util.externalizable.Externalizable;
import org.javarosa.core.util.externalizable.PrototypeFactory;

/**
 * Definition of a form. This has some meta data about the form definition and  
 * a collection of groups together with question branching or skipping rules.
 * 
 * @author <NAME>, <NAME>
 *
 */
public class FormDef implements IFormElement, Localizable, IDRecordable, Externalizable{
	private Vector children;	/** A collection of group definitions. */
	private int id;		/** The numeric unique identifier of the form definition on the local device */	
	private String title;	/** The display title of the form. */
	private String name;  /** A unique external name that is used to identify the form between machines */
	private Localizer localizer;
	public Vector triggerables; //<Triggerable>
	private DataModelTree model;

	private Hashtable triggerIndex; // <TreeReference, Vector<Triggerable>>
	private Hashtable conditionRepeatTargetIndex; //<TreeReference, Condition>; associates repeatable nodes with the Condition that determines their relevancy
	private EvaluationContext exprEvalContext;
	
	private QuestionPreloader preloader = new QuestionPreloader();

	public FormDef() {
		setChildren(null);
		triggerables = new Vector();
		triggerIndex = new Hashtable();
		conditionRepeatTargetIndex = new Hashtable();
		exprEvalContext = new EvaluationContext();
	}
	
	public Vector getChildren() {
		return children;
	}

	public void setChildren(Vector children) {
		this.children = (children == null ? new Vector() : children);
	}
	
	public void addChild (IFormElement fe) {
		children.addElement(fe);
	}
	
	public IFormElement getChild (int i) {
		if (i < children.size()) {
			return (IFormElement)children.elementAt(i);
		} else {
			return null;
		}
	}
	
	public IFormElement getChild (FormIndex index) {
		IFormElement element = this;
		while (index != null) {
			element =  element.getChild(index.getLocalIndex());
			index = index.getNextLevel();
		}
		return element;
	}
	
	/**
	 * Dereference the form index and return a Vector of all interstitial nodes (top-level parent first; index target last)
	 * 
	 * Ignore 'new-repeat' node for now; just return/stop at ref to yet-to-be-created repeat node (similar to repeats that already exist)
	 * 
	 * @param index
	 * @return
	 */
	public Vector explodeIndex (FormIndex index) {
		Vector indexes = new Vector();
		Vector multiplicities = new Vector();
		Vector elements = new Vector();
		
		collapseIndex(index, indexes, multiplicities, elements);		
		return elements;
	}
	
	//take a reference, find the instance node it refers to (factoring in multiplicities)
	public TreeReference getChildInstanceRef (FormIndex index) {
		Vector indexes = new Vector();
		Vector multiplicities = new Vector();
		Vector elements = new Vector();
		
		collapseIndex(index, indexes, multiplicities, elements);		
		return getChildInstanceRef(elements, multiplicities);
	}

	public TreeReference getChildInstanceRef (Vector elements, Vector multiplicities) {
		if (elements.size() == 0)
			return null;
				
		//get reference for target element
		TreeReference ref = DataModelTree.unpackReference(((IFormElement)elements.lastElement()).getBind()).clone();
		for (int i = 0; i < ref.size(); i++) {
			ref.multiplicity.setElementAt(new Integer(0), i);
		}
		
		//fill in multiplicities for repeats along the way
		for (int i = 0; i < elements.size(); i++) {
			IFormElement temp = (IFormElement)elements.elementAt(i);
			if (temp instanceof GroupDef && ((GroupDef)temp).getRepeat()) {
				TreeReference repRef = DataModelTree.unpackReference(temp.getBind());
				if (repRef.isParentOf(ref, false)) {
					int repMult = ((Integer)multiplicities.elementAt(i)).intValue();
					ref.multiplicity.setElementAt(new Integer(repMult), repRef.size() - 1);
				} else {
					return null; //question/repeat hierarchy is not consistent with instance model and bindings
				}
			}
		}
		
		return ref;
	}
	
	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public int getID() {
		return id;
	}

	public void setID(int id) {
		this.id = id;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	//treating id and record id as the same until we resolve the need for both of them
	public int getRecordId () {
		return getID();
	}
	
	public void setRecordId(int recordId) {
		setID(recordId);
	}
	
	public Localizer getLocalizer () {
		return localizer;
	}
	
	public void setLocalizer (Localizer l) {
		if (this.localizer != null) {
			this.localizer.unregisterLocalizable(this);
		}
		
		this.localizer = l;
		if (this.localizer != null) {
			this.localizer.registerLocalizable(this);
		}
	}
	
	public DataModelTree getDataModel () {
		return model;
	}
	
	public void setDataModel (IFormDataModel model) {
		this.model = (DataModelTree)model;
	}
	
	//don't think this should ever be called
	public IDataReference getBind () {
		return null;
	}
	
	public void setValue (IAnswerData data, TreeReference ref, int depth) {
		setValue(data, ref, model.resolveReference(ref), depth);
	}
	
	public void setValue (IAnswerData data, TreeReference ref, TreeElement node) {
		setValue(data, ref, node, 0);
	}
		
	//do we ever need to run this w/o evaluating conditions? or will we always just call on node directly?
	public void setValue (IAnswerData data, TreeReference ref, TreeElement node, int depth) {
		node.setAnswer(data);
		evaluateTriggerables(ref, depth);		
	}
	
	public void deleteRepeat (FormIndex index) {  
		TreeReference delRef = getChildInstanceRef(index);
	    TreeElement deleteChild = model.resolveReference(delRef);
	    
        TreeReference parentReferece = delRef.getParentRef();
        TreeElement parent = model.resolveReference(parentReferece);

        int childMult = deleteChild.getMult();
        parent.removeChild(deleteChild);
        Vector v = parent.getChildren();
        if (v != null) {
	        for (int i = 0; i < v.size(); i++) {
	        	TreeElement child = (TreeElement)v.elementAt(i);
	            if (child.getMult() > childMult) {
	                child.setMult(child.getMult() - 1);
	            }
	        }
        }
        evaluateTriggerables(parentReferece);  
	}
		
	public void createNewRepeat (FormIndex index) {
		TreeReference destRef = getChildInstanceRef(index);
		TreeElement template = model.getTemplate(destRef);
		
		model.copyNode(template, destRef);

		preloadModel(model.resolveReference(destRef));
		evaluateTriggerables(destRef); //trigger conditions that depend on the creation of this new node
		initializeTriggerables(destRef); //initialize conditions for the node (and sub-nodes)
	}
	
	public boolean canCreateRepeat (TreeReference repeatRef) {
		Condition c = (Condition)conditionRepeatTargetIndex.get(repeatRef.genericize());
		if (c != null) {<commit_msg>[r2444] deleteRepeat should work now for repeats when the repeated node is an ancestor of the parent of the current node<commit_after>/*
 * Copyright (C) 2009 JavaRosa-Core Project
 *
 * 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 org.javarosa.core.model;
 
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;

import org.javarosa.core.model.condition.Condition;
import org.javarosa.core.model.condition.Constraint;
import org.javarosa.core.model.condition.EvaluationContext;
import org.javarosa.core.model.condition.Recalculate;
import org.javarosa.core.model.condition.Triggerable;
import org.javarosa.core.model.data.IAnswerData;
import org.javarosa.core.model.data.SelectMultiData;
import org.javarosa.core.model.data.SelectOneData;
import org.javarosa.core.model.data.helper.Selection;
import org.javarosa.core.model.instance.DataModelTree;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.utils.Localizable;
import org.javarosa.core.model.utils.Localizer;
import org.javarosa.core.model.utils.QuestionPreloader;
import org.javarosa.core.services.storage.utilities.IDRecordable;
import org.javarosa.core.util.externalizable.DeserializationException;
import org.javarosa.core.util.externalizable.ExtUtil;
import org.javarosa.core.util.externalizable.ExtWrapList;
import org.javarosa.core.util.externalizable.ExtWrapListPoly;
import org.javarosa.core.util.externalizable.ExtWrapNullable;
import org.javarosa.core.util.externalizable.Externalizable;
import org.javarosa.core.util.externalizable.PrototypeFactory;

/**
 * Definition of a form. This has some meta data about the form definition and  
 * a collection of groups together with question branching or skipping rules.
 * 
 * @author <NAME>, <NAME>
 *
 */
public class FormDef implements IFormElement, Localizable, IDRecordable, Externalizable{
	private Vector children;	/** A collection of group definitions. */
	private int id;		/** The numeric unique identifier of the form definition on the local device */	
	private String title;	/** The display title of the form. */
	private String name;  /** A unique external name that is used to identify the form between machines */
	private Localizer localizer;
	public Vector triggerables; //<Triggerable>
	private DataModelTree model;

	private Hashtable triggerIndex; // <TreeReference, Vector<Triggerable>>
	private Hashtable conditionRepeatTargetIndex; //<TreeReference, Condition>; associates repeatable nodes with the Condition that determines their relevancy
	private EvaluationContext exprEvalContext;
	
	private QuestionPreloader preloader = new QuestionPreloader();

	public FormDef() {
		setChildren(null);
		triggerables = new Vector();
		triggerIndex = new Hashtable();
		conditionRepeatTargetIndex = new Hashtable();
		exprEvalContext = new EvaluationContext();
	}
	
	public Vector getChildren() {
		return children;
	}

	public void setChildren(Vector children) {
		this.children = (children == null ? new Vector() : children);
	}
	
	public void addChild (IFormElement fe) {
		children.addElement(fe);
	}
	
	public IFormElement getChild (int i) {
		if (i < children.size()) {
			return (IFormElement)children.elementAt(i);
		} else {
			return null;
		}
	}
	
	public IFormElement getChild (FormIndex index) {
		IFormElement element = this;
		while (index != null) {
			element =  element.getChild(index.getLocalIndex());
			index = index.getNextLevel();
		}
		return element;
	}
	
	/**
	 * Dereference the form index and return a Vector of all interstitial nodes (top-level parent first; index target last)
	 * 
	 * Ignore 'new-repeat' node for now; just return/stop at ref to yet-to-be-created repeat node (similar to repeats that already exist)
	 * 
	 * @param index
	 * @return
	 */
	public Vector explodeIndex (FormIndex index) {
		Vector indexes = new Vector();
		Vector multiplicities = new Vector();
		Vector elements = new Vector();
		
		collapseIndex(index, indexes, multiplicities, elements);		
		return elements;
	}
	
	//take a reference, find the instance node it refers to (factoring in multiplicities)
	public TreeReference getChildInstanceRef (FormIndex index) {
		Vector indexes = new Vector();
		Vector multiplicities = new Vector();
		Vector elements = new Vector();
		
		collapseIndex(index, indexes, multiplicities, elements);		
		return getChildInstanceRef(elements, multiplicities);
	}

	public TreeReference getChildInstanceRef (Vector elements, Vector multiplicities) {
		if (elements.size() == 0)
			return null;
				
		//get reference for target element
		TreeReference ref = DataModelTree.unpackReference(((IFormElement)elements.lastElement()).getBind()).clone();
		for (int i = 0; i < ref.size(); i++) {
			ref.multiplicity.setElementAt(new Integer(0), i);
		}
		
		//fill in multiplicities for repeats along the way
		for (int i = 0; i < elements.size(); i++) {
			IFormElement temp = (IFormElement)elements.elementAt(i);
			if (temp instanceof GroupDef && ((GroupDef)temp).getRepeat()) {
				TreeReference repRef = DataModelTree.unpackReference(temp.getBind());
				if (repRef.isParentOf(ref, false)) {
					int repMult = ((Integer)multiplicities.elementAt(i)).intValue();
					ref.multiplicity.setElementAt(new Integer(repMult), repRef.size() - 1);
				} else {
					return null; //question/repeat hierarchy is not consistent with instance model and bindings
				}
			}
		}
		
		return ref;
	}
	
	public String getTitle() {
		return title;
	}

	public void setTitle(String title) {
		this.title = title;
	}

	public int getID() {
		return id;
	}

	public void setID(int id) {
		this.id = id;
	}
	
	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}
	
	//treating id and record id as the same until we resolve the need for both of them
	public int getRecordId () {
		return getID();
	}
	
	public void setRecordId(int recordId) {
		setID(recordId);
	}
	
	public Localizer getLocalizer () {
		return localizer;
	}
	
	public void setLocalizer (Localizer l) {
		if (this.localizer != null) {
			this.localizer.unregisterLocalizable(this);
		}
		
		this.localizer = l;
		if (this.localizer != null) {
			this.localizer.registerLocalizable(this);
		}
	}
	
	public DataModelTree getDataModel () {
		return model;
	}
	
	public void setDataModel (IFormDataModel model) {
		this.model = (DataModelTree)model;
	}
	
	//don't think this should ever be called
	public IDataReference getBind () {
		return null;
	}
	
	public void setValue (IAnswerData data, TreeReference ref, int depth) {
		setValue(data, ref, model.resolveReference(ref), depth);
	}
	
	public void setValue (IAnswerData data, TreeReference ref, TreeElement node) {
		setValue(data, ref, node, 0);
	}
		
	//do we ever need to run this w/o evaluating conditions? or will we always just call on node directly?
	public void setValue (IAnswerData data, TreeReference ref, TreeElement node, int depth) {
		node.setAnswer(data);
		evaluateTriggerables(ref, depth);
	}

	/**
	 * Deletes the inner-most repeat that this node belongs to and returns the
	 * corresponding FormIndex. Behavior is currently undefined if you call this
	 * method on a node that is not contained within a repeat.
	 * 
	 * @param index
	 * @return
	 */
	public FormIndex deleteRepeat (FormIndex index) {
		Vector indexes = new Vector();
		Vector multiplicities = new Vector();
		Vector elements = new Vector();
		collapseIndex(index, indexes, multiplicities, elements);

		// loop backwards through the elements, removing objects from each
		// vector, until we find a repeat
		// TODO: should probably check to make sure size > 0
		for (int i = elements.size() - 1; i >= 0; i--) {
			IFormElement e = (IFormElement) elements.elementAt(i);
			if (e instanceof GroupDef && ((GroupDef) e).getRepeat()) {
				break;
			} else {
				indexes.removeElementAt(i);
				multiplicities.removeElementAt(i);
				elements.removeElementAt(i);
			}
		}

		// build new formIndex which includes everything
		// up to the node we're going to remove
		FormIndex deleteIndex = buildIndex(indexes, multiplicities, elements);

		TreeReference deleteRef = getChildInstanceRef(deleteIndex);
		TreeElement deleteElement = model.resolveReference(deleteRef);
		TreeReference parentRef = deleteRef.getParentRef();
		TreeElement parentElement = model.resolveReference(parentRef);

		int childMult = deleteElement.getMult();
		parentElement.removeChild(deleteElement);

		// update multiplicities of other child nodes
		Vector v = parentElement.getChildren();
		for (int i = 0; i < v.size(); i++) {
			TreeElement child = (TreeElement) v.elementAt(i);
			if (child.getMult() > childMult) {
				child.setMult(child.getMult() - 1);
			}
		}

		evaluateTriggerables(parentRef);
		return deleteIndex;
	}

	public void createNewRepeat (FormIndex index) {
		TreeReference destRef = getChildInstanceRef(index);
		TreeElement template = model.getTemplate(destRef);
		
		model.copyNode(template, destRef);

		preloadModel(model.resolveReference(destRef));
		evaluateTriggerables(destRef); //trigger conditions that depend on the creation of this new node
		initializeTriggerables(destRef); //initialize conditions for the node (and sub-nodes)
	}
	
	public boolean canCreateRepeat (TreeReference repeatRef) {
		Condition c = (Condition)conditionRepeatTargetIndex.get(repeatRef.genericize());
		if (c != null) {
{'fraction_non_alphanumeric': 0.09986789960369881, 'fraction_numerical': 0.002060766182298547, 'mean_word_length': 4.212338198843294, 'pattern_counts': {'":': 0, '<': 23, '<?xml version=': 0, '>': 19, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7251676', 'n_tokens_mistral': 5984, 'n_tokens_neox': 5413, 'n_words': 1672}