<commit_before>/*
 * Copyright (C) 2009 JavaRosa
 *
 * 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.NoSuchElementException;
import java.util.Vector;

import org.javarosa.core.log.WrappedException;
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.IConditionExpr;
import org.javarosa.core.model.condition.IFunctionHandler;
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.IntegerData;
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.AbstractTreeElement;
import org.javarosa.core.model.instance.DataInstance;
import org.javarosa.core.model.instance.FormInstance;
import org.javarosa.core.model.instance.InstanceInitializationFactory;
import org.javarosa.core.model.instance.InvalidReferenceException;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.util.restorable.RestoreUtils;
import org.javarosa.core.model.utils.QuestionPreloader;
import org.javarosa.core.services.locale.Localizable;
import org.javarosa.core.services.locale.Localizer;
import org.javarosa.core.services.storage.IMetaData;
import org.javarosa.core.services.storage.Persistable;
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.ExtWrapMap;
import org.javarosa.core.util.externalizable.ExtWrapNullable;
import org.javarosa.core.util.externalizable.ExtWrapTagged;
import org.javarosa.core.util.externalizable.PrototypeFactory;
import org.javarosa.xpath.XPathException;
import org.javarosa.xpath.XPathTypeMismatchException;

/**
 * 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, Persistable, IMetaData {
	public static final String STORAGE_KEY = "FORMDEF";
	public static final int TEMPLATING_RECURSION_LIMIT = 10;

	private Vector children;// <IFormElement> 
	/** 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;
	
	private Vector<XFormExtension> extensions;
	
	/**
	 * A unique external name that is used to identify the form between machines
	 */
	private Localizer localizer;
	public Vector<Triggerable> triggerables; // <Triggerable>; this list is topologically ordered, meaning for any tA and tB in
	//the list, where tA comes before tB, evaluating tA cannot depend on any result from evaluating tB
	private boolean triggerablesInOrder; //true if triggerables has been ordered topologically (DON'T DELETE ME EVEN THOUGH I'M UNUSED)
	

	private Vector outputFragments; // <IConditionExpr> contents of <output>
	// tags that serve as parameterized
	// arguments to captions

	public Hashtable<TreeReference, Vector<Triggerable>> triggerIndex;
	private Hashtable<TreeReference, Condition> conditionRepeatTargetIndex;
	// associates repeatable
	// nodes with the Condition
	// that determines their
	// relevancy
	public EvaluationContext exprEvalContext;

	private QuestionPreloader preloader = new QuestionPreloader();
	
	//XML ID's cannot start with numbers, so this should never conflict
	private static String DEFAULT_SUBMISSION_PROFILE = "1";
	
	private Hashtable<String,SubmissionProfile> submissionProfiles;

	private Hashtable<String, DataInstance> formInstances;
	private FormInstance mainInstance = null;

	Hashtable<String, Vector<Action>> eventListeners;


	/**
	 * 
	 */
	public FormDef() {
		setID(-1);
		setChildren(null);
		triggerables = new Vector();
		triggerablesInOrder = true;
		triggerIndex = new Hashtable();
		//This is kind of a wreck...
		setEvaluationContext(new EvaluationContext(null));
		outputFragments = new Vector();
		submissionProfiles = new Hashtable<String, SubmissionProfile>();
		formInstances = new Hashtable<String, DataInstance>();
		eventListeners = new Hashtable<String, Vector<Action>>();
		extensions = new Vector<XFormExtension>();
	}
	
	
	
	

	/**
	 * Getters and setters for the vectors tha
	 */
	public void addNonMainInstance(DataInstance instance)
	{
		formInstances.put(instance.getInstanceId(), instance);
		this.setEvaluationContext(new EvaluationContext(null));
	}
	
	/**
	 * Get an instance based on a name
	 * @param name string name
	 * @return
	 */
	public DataInstance getNonMainInstance(String name)
	{
		if(!formInstances.containsKey(name)) {
			return null;
		}
		return formInstances.get(name);
	}
	
	/**
	 * Get the non main instances
	 * @return
	 */
	public Enumeration getNonMainInstances()
	{
		return formInstances.elements();
	}
	
	/**
	 * Set the main instance
	 * @param fi
	 */
	public void setInstance(FormInstance fi)
	{
		mainInstance = fi;
		fi.setFormId(getID());
		this.setEvaluationContext(new EvaluationContext(null));
		attachControlsToInstanceData();
	}
	
	/**
	 * Get the main instance
	 * @return
	 */
	public FormInstance getMainInstance()
	{
		return mainInstance;
	}
	
	public FormInstance getInstance()
	{
		return getMainInstance();
	}
	
	public void fireEvent() {
		
	}

	
	// ---------- child elements
	public void addChild(IFormElement fe) {
		this.children.addElement(fe);
	}

	public IFormElement getChild(int i) {
		if (i < this.children.size())
			return (IFormElement) this.children.elementAt(i);

		throw new ArrayIndexOutOfBoundsException(
				"FormDef: invalid child index: " + i + " only "
						+ children.size() + " children");
	}

	public IFormElement getChild(FormIndex index) {
		IFormElement element = this;
		while (index != null && index.isInForm()) {
			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)
	/**
	 * @param index
	 * @return
	 */
	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);
	}

	/**
	 * @param elements
	 * @param multiplicities
	 * @return
	 */
	public TreeReference getChildInstanceRef(Vector elements, Vector multiplicities) {
		if (elements.size() == 0)
			return null;

		// get reference for target element
		TreeReference ref = FormInstance.unpackReference(((IFormElement) elements.lastElement()).getBind()).clone();
		for (int i = 0; i < ref.size(); i++) {
			//There has to be a better way to encapsulate this
			if(ref.getMultiplicity(i) != TreeReference.INDEX_ATTRIBUTE) {
				ref.setMultiplicity(i, 0);
			}
		}

		// 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 = FormInstance.unpackReference(temp.getBind());
				if (repRef.isParentOf(ref, false)) {
					int repMult = ((Integer) multiplicities.elementAt(i)).intValue();
					ref.setMultiplicity(repRef.size() - 1, repMult);
				} else {
					return null; // question/repeat hierarchy is not consistent
					// with instance instance and bindings
				}
			}
		}

		return ref;
	}

	public void setLocalizer(Localizer l) {
		if (this.localizer != null) {
			this.localizer.unregisterLocalizable(this);
		}

		this.localizer = l;
		if (this.localizer != null) {
			this.localizer.registerLocalizable(this);
		}
	}

	// don't think this should ever be called(!)
	public IDataReference getBind() {
		throw new RuntimeException("method not implemented");
	}

	public void setValue(IAnswerData data, TreeReference ref) {
		setValue(data, ref, mainInstance.resolveReference(ref));
	}

	public void setValue(IAnswerData data, TreeReference ref, TreeElement node) {
		setAnswer(data, node);
		triggerTriggerables(ref);
		//TODO: pre-populate fix-count repeats here?
	}

	public void setAnswer(IAnswerData data, TreeReference ref) {
		setAnswer(data, mainInstance.resolveReference(ref));
	}
	
	public void setAnswer(IAnswerData data, TreeElement node) {
		node.setAnswer(data);
	}
	
	/**
	 * 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 newIndex = buildIndex(indexes, multiplicities, elements);

		TreeReference deleteRef = getChildInstanceRef(newIndex);
		TreeElement deleteElement = mainInstance.resolveReference(deleteRef);
		TreeReference parentRef = deleteRef.getParentRef();
		TreeElement parentElement = mainInstance.resolveReference(parentRef);

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

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

		triggerTriggerables(deleteRef);
		return newIndex;
	}

	public void createNewRepeat(FormIndex index) throws InvalidReferenceException {
		TreeReference destRef = getChildInstanceRef(index);
		TreeElement template = mainInstance.getTemplate(destRef);

		mainInstance.copyNode(template, destRef);

		preloadInstance(mainInstance.resolveReference(destRef));
		
		//2013-05-14 - ctsims - Events should get fired _before_ calculate stuff is fired, moved
		//this above triggering triggerables 
		//Grab any actions listening to this event
		Vector<Action> listeners = getEventListeners(Action.EVENT_JR_INSERT);
		for(Action a : listeners) {
			a.processAction(this, destRef);
		}
		
		triggerTriggerables(destRef); // trigger conditions that depend on the creation of this new node
		initializeTriggerables(destRef); // initialize conditions for the node (and sub-nodes)
	}
	
	public boolean isRepeatRelevant (TreeReference repeatRef) {
		boolean relev = true;
		
		Condition c = (Condition) conditionRepeatTargetIndex.get(repeatRef.genericize());
		if (c != null) {
			relev = c.evalBool(mainInstance, new EvaluationContext(exprEvalContext, repeatRef));
		}

		//check the relevancy of the immediate parent
		if (relev) {
			TreeElement templNode = mainInstance.getTemplate(repeatRef);
			TreeReference parentPath = templNode.getParent().getRef().genericize();
			TreeElement parentNode = mainInstance.resolveReference(parentPath.contextualize(repeatRef));
			relev = parentNode.isRelevant();
		}
	
		return relev;
	}

	public boolean canCreateRepeat(TreeReference repeatRef, FormIndex repeatIndex) {
		GroupDef repeat = (GroupDef)this.getChild(repeatIndex);
		
		//Check to see if this repeat can have children added by the user
		if(repeat.noAddRemove) {
			//Check to see if there's a count to use to determine how many children this repeat
			//should have
			if(repeat.getCountReference() != null) {
				int currentMultiplicity = repeatIndex.getElementMultiplicity();
				
				AbstractTreeElement countNode = this.getMainInstance().resolveReference(repeat.getConextualizedCountReference(repeatRef));
				if(countNode == null) {
					throw new XPathTypeMismatchException("Could not find the location " + repeat.getConextualizedCountReference(repeatRef).toString() + " where the repeat at " + repeatRef.toString(false) + " is looking for its count");
				}
				//get the total multiplicity possible
				IAnswerData count = countNode.getValue();
				int fullcount = -1;
				if(count == null) {
					fullcount = 0;
				} else {
					try {
						fullcount = ((Integer)new IntegerData().cast(count.uncast()).getValue()).intValue();
					} catch(IllegalArgumentException iae) {
						throw new XPathTypeMismatchException("The repeat count value \"" + count.uncast().getString() + "\" at " + repeat.getConextualizedCountReference(repeatRef).toString() + " must be a number!");
					}
				}
						
				if(fullcount <= currentMultiplicity) {
					return false;
				}
			} else {
				//Otherwise the user can never add repeat instances 
				return false;
			}
		}
		
		//TODO: If we think the node is still relevant, we also need to figure out a way to test that assumption against
		//the repeat's constraints.
		
		
		return true;
	}
	
	public void copyItemsetAnswer(QuestionDef q, TreeElement targetNode, IAnswerData data) throws InvalidReferenceException{
		ItemsetBinding itemset = q.getDynamicChoices();
		TreeReference targetRef = targetNode.getRef();
		TreeReference destRef = itemset.getDestRef().contextualize(targetRef);

		Vector<Selection> selections = null;
		Vector<String> selectedValues = new Vector<String>();
		if (data instanceof SelectMultiData) {
			selections = (Vector<Selection>)data.getValue();
		} else if (data instanceof SelectOneData) {
			selections = new Vector<Selection>();
			selections.addElement((Selection)data.getValue());
		}
		if (itemset.valueRef != null) {
			for (int i = 0; i < selections.size(); i++) {
				selectedValues.addElement(selections.elementAt(i).choice.getValue());
			}
		}
					
		//delete existing dest nodes that are not in the answer selection
		Hashtable<String, TreeElement> existingValues = new Hashtable<String, TreeElement>();
		Vector<TreeReference> existingNodes = exprEvalContext.expandReference(destRef);
		for (int i = 0; i < existingNodes.size(); i++) {
			TreeElement node = getMainInstance().resolveReference(existingNodes.elementAt(i));
			
			if (itemset.valueRef != null) {
				String value = itemset.getRelativeValue().evalReadable(this.getMainInstance(), new EvaluationContext(exprEvalContext, node.getRef()));
				if (selectedValues.contains(value)) {
					existingValues.put(value, node); //cache node if in selection and already exists
				}
			}

			//delete from target
			targetNode.removeChild(node);
		}
		
		//copy in nodes for new answer; preserve ordering in answer
		for (int i = 0; i < selections.size(); i++) {
			Selection s = selections.elementAt(i);
			SelectChoice ch = s.choice;
			
			TreeElement cachedNode = null;
			if (itemset.valueRef != null) {
				String value = ch.getValue();
				if (existingValues.containsKey(value)) {
					cachedNode = existingValues.get(value);
				}
			}

			if (cachedNode != null) {
				cachedNode.setMult(i);
				targetNode.addChild(cachedNode);
			} else {
				getMainInstance().copyItemsetNode(ch.copyNode, destRef, this);
			}
		}
		
		triggerTriggerables(destRef); // trigger conditions that depend on the creation of these new nodes
		initializeTriggerables(destRef); // initialize conditions for the node (and sub-nodes)
		  //not 100% sure this will work since destRef is ambiguous as the last step, but i think it's supposed to work
	}
	
	/**
	 * Add a Condition to the form's Collection.
	 * 
	 * @param condition
	 *            the condition to be set
	 */
	public Triggerable addTriggerable(Triggerable t) {
		int existingIx = triggerables.indexOf(t);
		if (existingIx >= 0) {
			//one node may control access to many nodes; this means many nodes effectively have the same condition
			//let's identify when conditions are the same, and store and calculate it only once
			
			//nov-2-2011: ctsims - We need to merge the context nodes together whenever we do this (finding the highest
			//common ground between the two), otherwise we can end up failing to trigger when the ignored context
			//exists and the used one doesn't
			
			Triggerable existingTriggerable = (Triggerable)triggerables.elementAt(existingIx);
			
			existingTriggerable.contextRef = existingTriggerable.contextRef.intersect(t.contextRef);
			
			return existingTriggerable;

			//note, if the contextRef is unnecessarily deep, the condition will be evaluated more times than needed
			//perhaps detect when 'identical' condition has a shorter contextRef, and use that one instead?
			
		} else {
			triggerables.addElement(t);
			triggerablesInOrder = false;

			Vector triggers = t.getTriggers();
			for (int i = 0; i < triggers.size(); i++) {
				TreeReference trigger = (TreeReference) triggers.elementAt(i);
				if (!triggerIndex.containsKey(trigger)) {
					triggerIndex.put(trigger.clone(), new Vector());
				}
				Vector triggered = (Vector) triggerIndex.get(trigger);
				if (!triggered.contains(t)) {
					triggered.addElement(t);
				}
			}
			
			return t;
		}
	}

	/**
	 * Finalize the DAG associated with the form's triggered conditions. This will create
	 * the appropriate ordering and dependencies to ensure the conditions will be evaluated
	 * in the appropriate orders.
	 * 
	 * @throws IllegalStateException - If the trigger ordering contains an illegal cycle and the
	 * triggers can't be laid out appropriately
	 */
	public void finalizeTriggerables () throws IllegalStateException {
		//
		//DAGify the triggerables based on dependencies and sort them so that
		//trigbles come only after the trigbles they depend on
		//
		
		Vector partialOrdering = new Vector();
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			
			Vector deps = new Vector();
			fillTriggeredElements(t, deps);
			
			for (int j = 0; j < deps.size(); j++) {
				Triggerable u = (Triggerable)deps.elementAt(j);
				Triggerable[] edge = {t, u};
				partialOrdering.addElement(edge);
			}
		}
		
		Vector<Triggerable> vertices = new Vector<Triggerable>();
		for (int i = 0; i < triggerables.size(); i++)
			vertices.addElement(triggerables.elementAt(i));
		triggerables.removeAllElements();
		
		while (vertices.size() > 0) {
			//determine root nodes
			Vector<Triggerable> roots = new Vector<Triggerable>();
			for (int i = 0; i < vertices.size(); i++) {
				roots.addElement(vertices.elementAt(i));
			}
			for (int i = 0; i < partialOrdering.size(); i++) {
				Triggerable[] edge = (Triggerable[])partialOrdering.elementAt(i);
				roots.removeElement(edge[1]);
			}
			
			//if no root nodes while graph still has nodes, graph has cycles
			if (roots.size() == 0) {
				String hints = "";
				for(Triggerable t : vertices) {
					for(TreeReference r : t.getTargets()) {
						hints += "\n" + r.toString(true);
					}
				}
				String message = "Cycle detected in form's relevant and calculation logic!";
				if(!hints.equals("")) {
					message += "\nThe following nodes are likely involved in the loop:" + hints;
				}
				throw new IllegalStateException(message);
			}

			//remove root nodes and edges originating from them
			for (int i = 0; i < roots.size(); i++) {
				Triggerable root = (Triggerable)roots.elementAt(i);
				triggerables.addElement(root);
				vertices.removeElement(root);
			}			
			for (int i = partialOrdering.size() - 1; i >= 0; i--) {
				Triggerable[] edge = (Triggerable[])partialOrdering.elementAt(i);
				if (roots.contains(edge[0]))
					partialOrdering.removeElementAt(i);
			}
		}
		
		triggerablesInOrder = true;
		
		//
		//build the condition index for repeatable nodes
		//
		
		conditionRepeatTargetIndex = new Hashtable();
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			if (t instanceof Condition) {
				Vector targets = t.getTargets();
				for (int j = 0; j < targets.size(); j++) {
					TreeReference target = (TreeReference) targets.elementAt(j);
					if (mainInstance.getTemplate(target) != null) {
						conditionRepeatTargetIndex.put(target, (Condition) t);
					}
				}
			}
		}

	}
		
	/**
	 * Get all of the elements which will need to be evaluated (in order) when the
	 * triggerable is fired.
	 * @param t
	 */
	public void fillTriggeredElements(Triggerable t, Vector<Triggerable> destination) {
		if (t.canCascade()) {
			for (int j = 0; j < t.getTargets().size(); j++) {
				TreeReference target = (TreeReference)t.getTargets().elementAt(j);
				Vector<TreeReference> updatedNodes = new Vector<TreeReference>();
				updatedNodes.addElement(target);
				
				//For certain types of triggerables, the update will affect not only the target, but
				//also the children of the target. In that case, we want to add all of those nodes 
				//to the list of updated elements as well.
				if(t.isCascadingToChildren()) {
					addChildrenOfReference(target, updatedNodes);
				}
				
				//Now go through each of these updated nodes (generally just 1 for a normal calculation,
				//multiple nodes if there's a relevance cascade.
				for(TreeReference ref : updatedNodes) {
					//Check our index to see if that target is a Trigger for other conditions
					//IE: if they are an element of a different calculation or relevancy calc
					
					//We can't make this reference generic before now or we'll lose the target information,
					//so we'll be more inclusive than needed and see if any of our triggers are keyed on
					//the predicate-less path of this ref
					Vector<Triggerable> triggered = (Vector<Triggerable>)triggerIndex.get(ref.hasPredicates() ? ref.removePredicates() : ref);
					
					if (triggered != null) {
						//If so, walk all of these triggerables that we found
						for (int k = 0; k < triggered.size(); k++) {
							Triggerable u = (Triggerable)triggered.elementAt(k);
							
							//And add them to the queue if they aren't there already
							if (!destination.contains(u))
								destination.addElement(u);
						}
					}
				}

			}
		}
	}





	public void initializeTriggerables() {
		initializeTriggerables(TreeReference.rootRef());
	}

	/**
	 * Walks the current set of conditions, and evaluates each of them with the
	 * current context.
	 */
	private void initializeTriggerables(TreeReference rootRef) {
		TreeReference genericRoot = rootRef.genericize();

		Vector applicable = new Vector();
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			for (int j = 0; j < t.getTargets().size(); j++) {
				TreeReference target = (TreeReference)t.getTargets().elementAt(j);
				if (genericRoot.isParentOf(target, false)) {
					applicable.addElement(t);
					break;
				}
			}
		}
		
		evaluateTriggerables(applicable, rootRef);
	}
	
	/**
	 * The entry point for the DAG cascade after a value is changed in the model.
	 * 
	 * @param ref The full contextualized unambiguous reference of the value that was
	 * changed.
	 */
	public void triggerTriggerables(TreeReference ref) {
		
		//turn unambiguous ref into a generic ref
		//to identify what nodes should be triggered by this 
		//reference changing
		TreeReference genericRef = ref.genericize();

		//get triggerables which are activated by the generic reference
		Vector triggered = (Vector)triggerIndex.get(genericRef);
		if (triggered == null) {
			return;
		}

		//Our vector doesn't have a shallow copy op, so make one 
		Vector triggeredCopy = new Vector();
		for (int i = 0; i < triggered.size(); i++) { 
			triggeredCopy.addElement(triggered.elementAt(i));
		}
		
		//Evaluate all of the triggerables in our new vector
		evaluateTriggerables(triggeredCopy, ref);
	}

	/**
	 * Step 2 in evaluating DAG computation updates from a value being changed in 
	 * the instance. This step is responsible for taking the root set of directly
	 * triggered conditions, identifying which conditions should further be triggered
	 * due to their update, and then dispatching all of the evaluations.
	 * 
	 * @param tv A vector of all of the trigerrables directly triggered by the 
	 * value changed
	 * @param anchorRef The reference to original value that was updated
	 */
	private void evaluateTriggerables(Vector tv, TreeReference anchorRef) {
		//add all cascaded triggerables to queue
		
		//Iterate through all of the currently known triggerables to be triggered
		for (int i = 0; i < tv.size(); i++) {
			Triggerable t = (Triggerable)tv.elementAt(i);
			fillTriggeredElements(t, tv);
		}
		
		//tv should now contain all of the triggerable components which are going to need to be addressed
		//by this update.
		//'triggerables' is topologically-ordered by dependencies, so evaluate the triggerables in 'tv'
		//in the order they appear in 'triggerables'
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			if (tv.contains(t)) {
				evaluateTriggerable(t, anchorRef);
			}
		}
	}
	
	/**
	 * Step 3 in DAG cascade. evaluate the individual triggerable expressions against
	 * the anchor (the value that changed which triggered recomputation)
	 * 
	 * @param t The triggerable to be updated
	 * @param anchorRef The reference to the value which was changed.
	 */
	private void evaluateTriggerable(Triggerable t, TreeReference anchorRef) {
		
		//Contextualize the reference used by the triggerable against the anchor
		TreeReference contextRef = t.contextRef.contextualize(anchorRef);
		
		//Now identify all of the fully qualified nodes which this triggerable
		//updates. (Multiple nodes can be updated by the same trigger)
		Vector<TreeReference> v = exprEvalContext.expandReference(contextRef);
		
		//Go through each one and evaluate the trigger expresion
		for (int i = 0; i < v.size(); i++) {
			try {
				t.apply(mainInstance, exprEvalContext, v.elementAt(i), this);
			}catch(RuntimeException e) {
				throw e;
			}
		}
	}
	
	/**
	 * This is a utility method to get all of the references of a node. It can be replaced
	 * when we support dependent XPath Steps (IE: /path/to//)
	 */
	public void addChildrenOfReference(TreeReference original, Vector<TreeReference> toAdd) {
		for(TreeReference ref : exprEvalContext.expandReference(original)) {
			addChildrenOfElement(exprEvalContext.resolveReference(ref), toAdd);
		}
	}
	
	//Recursive step of utility method 
	private void addChildrenOfElement(AbstractTreeElement el, Vector<TreeReference> toAdd) {
		for(int i = 0 ; i < el.getNumChildren() ; ++i) {
			AbstractTreeElement child = el.getChildAt(i);
			toAdd.addElement(child.getRef().genericize());
			addChildrenOfElement(child, toAdd);
		}
	}

	public boolean evaluateConstraint(TreeReference ref, IAnswerData data) {<commit_msg>Fix for Relevancy Cascade algorithm failure which was caused by triggers not properly including attributes as "children" when cascading relevant conditions when expanding.<commit_after>/*
 * Copyright (C) 2009 JavaRosa
 *
 * 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.NoSuchElementException;
import java.util.Vector;

import org.javarosa.core.log.WrappedException;
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.IConditionExpr;
import org.javarosa.core.model.condition.IFunctionHandler;
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.IntegerData;
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.AbstractTreeElement;
import org.javarosa.core.model.instance.DataInstance;
import org.javarosa.core.model.instance.FormInstance;
import org.javarosa.core.model.instance.InstanceInitializationFactory;
import org.javarosa.core.model.instance.InvalidReferenceException;
import org.javarosa.core.model.instance.TreeElement;
import org.javarosa.core.model.instance.TreeReference;
import org.javarosa.core.model.util.restorable.RestoreUtils;
import org.javarosa.core.model.utils.QuestionPreloader;
import org.javarosa.core.services.locale.Localizable;
import org.javarosa.core.services.locale.Localizer;
import org.javarosa.core.services.storage.IMetaData;
import org.javarosa.core.services.storage.Persistable;
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.ExtWrapMap;
import org.javarosa.core.util.externalizable.ExtWrapNullable;
import org.javarosa.core.util.externalizable.ExtWrapTagged;
import org.javarosa.core.util.externalizable.PrototypeFactory;
import org.javarosa.xpath.XPathException;
import org.javarosa.xpath.XPathTypeMismatchException;

/**
 * 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, Persistable, IMetaData {
	public static final String STORAGE_KEY = "FORMDEF";
	public static final int TEMPLATING_RECURSION_LIMIT = 10;

	private Vector children;// <IFormElement> 
	/** 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;
	
	private Vector<XFormExtension> extensions;
	
	/**
	 * A unique external name that is used to identify the form between machines
	 */
	private Localizer localizer;
	public Vector<Triggerable> triggerables; // <Triggerable>; this list is topologically ordered, meaning for any tA and tB in
	//the list, where tA comes before tB, evaluating tA cannot depend on any result from evaluating tB
	private boolean triggerablesInOrder; //true if triggerables has been ordered topologically (DON'T DELETE ME EVEN THOUGH I'M UNUSED)
	

	private Vector outputFragments; // <IConditionExpr> contents of <output>
	// tags that serve as parameterized
	// arguments to captions

	public Hashtable<TreeReference, Vector<Triggerable>> triggerIndex;
	private Hashtable<TreeReference, Condition> conditionRepeatTargetIndex;
	// associates repeatable
	// nodes with the Condition
	// that determines their
	// relevancy
	public EvaluationContext exprEvalContext;

	private QuestionPreloader preloader = new QuestionPreloader();
	
	//XML ID's cannot start with numbers, so this should never conflict
	private static String DEFAULT_SUBMISSION_PROFILE = "1";
	
	private Hashtable<String,SubmissionProfile> submissionProfiles;

	private Hashtable<String, DataInstance> formInstances;
	private FormInstance mainInstance = null;

	Hashtable<String, Vector<Action>> eventListeners;


	/**
	 * 
	 */
	public FormDef() {
		setID(-1);
		setChildren(null);
		triggerables = new Vector();
		triggerablesInOrder = true;
		triggerIndex = new Hashtable();
		//This is kind of a wreck...
		setEvaluationContext(new EvaluationContext(null));
		outputFragments = new Vector();
		submissionProfiles = new Hashtable<String, SubmissionProfile>();
		formInstances = new Hashtable<String, DataInstance>();
		eventListeners = new Hashtable<String, Vector<Action>>();
		extensions = new Vector<XFormExtension>();
	}
	
	
	
	

	/**
	 * Getters and setters for the vectors tha
	 */
	public void addNonMainInstance(DataInstance instance)
	{
		formInstances.put(instance.getInstanceId(), instance);
		this.setEvaluationContext(new EvaluationContext(null));
	}
	
	/**
	 * Get an instance based on a name
	 * @param name string name
	 * @return
	 */
	public DataInstance getNonMainInstance(String name)
	{
		if(!formInstances.containsKey(name)) {
			return null;
		}
		return formInstances.get(name);
	}
	
	/**
	 * Get the non main instances
	 * @return
	 */
	public Enumeration getNonMainInstances()
	{
		return formInstances.elements();
	}
	
	/**
	 * Set the main instance
	 * @param fi
	 */
	public void setInstance(FormInstance fi)
	{
		mainInstance = fi;
		fi.setFormId(getID());
		this.setEvaluationContext(new EvaluationContext(null));
		attachControlsToInstanceData();
	}
	
	/**
	 * Get the main instance
	 * @return
	 */
	public FormInstance getMainInstance()
	{
		return mainInstance;
	}
	
	public FormInstance getInstance()
	{
		return getMainInstance();
	}
	
	public void fireEvent() {
		
	}

	
	// ---------- child elements
	public void addChild(IFormElement fe) {
		this.children.addElement(fe);
	}

	public IFormElement getChild(int i) {
		if (i < this.children.size())
			return (IFormElement) this.children.elementAt(i);

		throw new ArrayIndexOutOfBoundsException(
				"FormDef: invalid child index: " + i + " only "
						+ children.size() + " children");
	}

	public IFormElement getChild(FormIndex index) {
		IFormElement element = this;
		while (index != null && index.isInForm()) {
			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)
	/**
	 * @param index
	 * @return
	 */
	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);
	}

	/**
	 * @param elements
	 * @param multiplicities
	 * @return
	 */
	public TreeReference getChildInstanceRef(Vector elements, Vector multiplicities) {
		if (elements.size() == 0)
			return null;

		// get reference for target element
		TreeReference ref = FormInstance.unpackReference(((IFormElement) elements.lastElement()).getBind()).clone();
		for (int i = 0; i < ref.size(); i++) {
			//There has to be a better way to encapsulate this
			if(ref.getMultiplicity(i) != TreeReference.INDEX_ATTRIBUTE) {
				ref.setMultiplicity(i, 0);
			}
		}

		// 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 = FormInstance.unpackReference(temp.getBind());
				if (repRef.isParentOf(ref, false)) {
					int repMult = ((Integer) multiplicities.elementAt(i)).intValue();
					ref.setMultiplicity(repRef.size() - 1, repMult);
				} else {
					return null; // question/repeat hierarchy is not consistent
					// with instance instance and bindings
				}
			}
		}

		return ref;
	}

	public void setLocalizer(Localizer l) {
		if (this.localizer != null) {
			this.localizer.unregisterLocalizable(this);
		}

		this.localizer = l;
		if (this.localizer != null) {
			this.localizer.registerLocalizable(this);
		}
	}

	// don't think this should ever be called(!)
	public IDataReference getBind() {
		throw new RuntimeException("method not implemented");
	}

	public void setValue(IAnswerData data, TreeReference ref) {
		setValue(data, ref, mainInstance.resolveReference(ref));
	}

	public void setValue(IAnswerData data, TreeReference ref, TreeElement node) {
		setAnswer(data, node);
		triggerTriggerables(ref);
		//TODO: pre-populate fix-count repeats here?
	}

	public void setAnswer(IAnswerData data, TreeReference ref) {
		setAnswer(data, mainInstance.resolveReference(ref));
	}
	
	public void setAnswer(IAnswerData data, TreeElement node) {
		node.setAnswer(data);
	}
	
	/**
	 * 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 newIndex = buildIndex(indexes, multiplicities, elements);

		TreeReference deleteRef = getChildInstanceRef(newIndex);
		TreeElement deleteElement = mainInstance.resolveReference(deleteRef);
		TreeReference parentRef = deleteRef.getParentRef();
		TreeElement parentElement = mainInstance.resolveReference(parentRef);

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

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

		triggerTriggerables(deleteRef);
		return newIndex;
	}

	public void createNewRepeat(FormIndex index) throws InvalidReferenceException {
		TreeReference destRef = getChildInstanceRef(index);
		TreeElement template = mainInstance.getTemplate(destRef);

		mainInstance.copyNode(template, destRef);

		preloadInstance(mainInstance.resolveReference(destRef));
		
		//2013-05-14 - ctsims - Events should get fired _before_ calculate stuff is fired, moved
		//this above triggering triggerables 
		//Grab any actions listening to this event
		Vector<Action> listeners = getEventListeners(Action.EVENT_JR_INSERT);
		for(Action a : listeners) {
			a.processAction(this, destRef);
		}
		
		triggerTriggerables(destRef); // trigger conditions that depend on the creation of this new node
		initializeTriggerables(destRef); // initialize conditions for the node (and sub-nodes)
	}
	
	public boolean isRepeatRelevant (TreeReference repeatRef) {
		boolean relev = true;
		
		Condition c = (Condition) conditionRepeatTargetIndex.get(repeatRef.genericize());
		if (c != null) {
			relev = c.evalBool(mainInstance, new EvaluationContext(exprEvalContext, repeatRef));
		}

		//check the relevancy of the immediate parent
		if (relev) {
			TreeElement templNode = mainInstance.getTemplate(repeatRef);
			TreeReference parentPath = templNode.getParent().getRef().genericize();
			TreeElement parentNode = mainInstance.resolveReference(parentPath.contextualize(repeatRef));
			relev = parentNode.isRelevant();
		}
	
		return relev;
	}

	public boolean canCreateRepeat(TreeReference repeatRef, FormIndex repeatIndex) {
		GroupDef repeat = (GroupDef)this.getChild(repeatIndex);
		
		//Check to see if this repeat can have children added by the user
		if(repeat.noAddRemove) {
			//Check to see if there's a count to use to determine how many children this repeat
			//should have
			if(repeat.getCountReference() != null) {
				int currentMultiplicity = repeatIndex.getElementMultiplicity();
				
				AbstractTreeElement countNode = this.getMainInstance().resolveReference(repeat.getConextualizedCountReference(repeatRef));
				if(countNode == null) {
					throw new XPathTypeMismatchException("Could not find the location " + repeat.getConextualizedCountReference(repeatRef).toString() + " where the repeat at " + repeatRef.toString(false) + " is looking for its count");
				}
				//get the total multiplicity possible
				IAnswerData count = countNode.getValue();
				int fullcount = -1;
				if(count == null) {
					fullcount = 0;
				} else {
					try {
						fullcount = ((Integer)new IntegerData().cast(count.uncast()).getValue()).intValue();
					} catch(IllegalArgumentException iae) {
						throw new XPathTypeMismatchException("The repeat count value \"" + count.uncast().getString() + "\" at " + repeat.getConextualizedCountReference(repeatRef).toString() + " must be a number!");
					}
				}
						
				if(fullcount <= currentMultiplicity) {
					return false;
				}
			} else {
				//Otherwise the user can never add repeat instances 
				return false;
			}
		}
		
		//TODO: If we think the node is still relevant, we also need to figure out a way to test that assumption against
		//the repeat's constraints.
		
		
		return true;
	}
	
	public void copyItemsetAnswer(QuestionDef q, TreeElement targetNode, IAnswerData data) throws InvalidReferenceException{
		ItemsetBinding itemset = q.getDynamicChoices();
		TreeReference targetRef = targetNode.getRef();
		TreeReference destRef = itemset.getDestRef().contextualize(targetRef);

		Vector<Selection> selections = null;
		Vector<String> selectedValues = new Vector<String>();
		if (data instanceof SelectMultiData) {
			selections = (Vector<Selection>)data.getValue();
		} else if (data instanceof SelectOneData) {
			selections = new Vector<Selection>();
			selections.addElement((Selection)data.getValue());
		}
		if (itemset.valueRef != null) {
			for (int i = 0; i < selections.size(); i++) {
				selectedValues.addElement(selections.elementAt(i).choice.getValue());
			}
		}
					
		//delete existing dest nodes that are not in the answer selection
		Hashtable<String, TreeElement> existingValues = new Hashtable<String, TreeElement>();
		Vector<TreeReference> existingNodes = exprEvalContext.expandReference(destRef);
		for (int i = 0; i < existingNodes.size(); i++) {
			TreeElement node = getMainInstance().resolveReference(existingNodes.elementAt(i));
			
			if (itemset.valueRef != null) {
				String value = itemset.getRelativeValue().evalReadable(this.getMainInstance(), new EvaluationContext(exprEvalContext, node.getRef()));
				if (selectedValues.contains(value)) {
					existingValues.put(value, node); //cache node if in selection and already exists
				}
			}

			//delete from target
			targetNode.removeChild(node);
		}
		
		//copy in nodes for new answer; preserve ordering in answer
		for (int i = 0; i < selections.size(); i++) {
			Selection s = selections.elementAt(i);
			SelectChoice ch = s.choice;
			
			TreeElement cachedNode = null;
			if (itemset.valueRef != null) {
				String value = ch.getValue();
				if (existingValues.containsKey(value)) {
					cachedNode = existingValues.get(value);
				}
			}

			if (cachedNode != null) {
				cachedNode.setMult(i);
				targetNode.addChild(cachedNode);
			} else {
				getMainInstance().copyItemsetNode(ch.copyNode, destRef, this);
			}
		}
		
		triggerTriggerables(destRef); // trigger conditions that depend on the creation of these new nodes
		initializeTriggerables(destRef); // initialize conditions for the node (and sub-nodes)
		  //not 100% sure this will work since destRef is ambiguous as the last step, but i think it's supposed to work
	}
	
	/**
	 * Add a Condition to the form's Collection.
	 * 
	 * @param condition
	 *            the condition to be set
	 */
	public Triggerable addTriggerable(Triggerable t) {
		int existingIx = triggerables.indexOf(t);
		if (existingIx >= 0) {
			//one node may control access to many nodes; this means many nodes effectively have the same condition
			//let's identify when conditions are the same, and store and calculate it only once
			
			//nov-2-2011: ctsims - We need to merge the context nodes together whenever we do this (finding the highest
			//common ground between the two), otherwise we can end up failing to trigger when the ignored context
			//exists and the used one doesn't
			
			Triggerable existingTriggerable = (Triggerable)triggerables.elementAt(existingIx);
			
			existingTriggerable.contextRef = existingTriggerable.contextRef.intersect(t.contextRef);
			
			return existingTriggerable;

			//note, if the contextRef is unnecessarily deep, the condition will be evaluated more times than needed
			//perhaps detect when 'identical' condition has a shorter contextRef, and use that one instead?
			
		} else {
			triggerables.addElement(t);
			triggerablesInOrder = false;

			Vector triggers = t.getTriggers();
			for (int i = 0; i < triggers.size(); i++) {
				TreeReference trigger = (TreeReference) triggers.elementAt(i);
				if (!triggerIndex.containsKey(trigger)) {
					triggerIndex.put(trigger.clone(), new Vector());
				}
				Vector triggered = (Vector) triggerIndex.get(trigger);
				if (!triggered.contains(t)) {
					triggered.addElement(t);
				}
			}
			
			return t;
		}
	}

	/**
	 * Finalize the DAG associated with the form's triggered conditions. This will create
	 * the appropriate ordering and dependencies to ensure the conditions will be evaluated
	 * in the appropriate orders.
	 * 
	 * @throws IllegalStateException - If the trigger ordering contains an illegal cycle and the
	 * triggers can't be laid out appropriately
	 */
	public void finalizeTriggerables () throws IllegalStateException {
		//
		//DAGify the triggerables based on dependencies and sort them so that
		//trigbles come only after the trigbles they depend on
		//
		
		Vector partialOrdering = new Vector();
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			
			Vector deps = new Vector();
			fillTriggeredElements(t, deps);
			
			for (int j = 0; j < deps.size(); j++) {
				Triggerable u = (Triggerable)deps.elementAt(j);
				Triggerable[] edge = {t, u};
				partialOrdering.addElement(edge);
			}
		}
		
		Vector<Triggerable> vertices = new Vector<Triggerable>();
		for (int i = 0; i < triggerables.size(); i++)
			vertices.addElement(triggerables.elementAt(i));
		triggerables.removeAllElements();
		
		while (vertices.size() > 0) {
			//determine root nodes
			Vector<Triggerable> roots = new Vector<Triggerable>();
			for (int i = 0; i < vertices.size(); i++) {
				roots.addElement(vertices.elementAt(i));
			}
			for (int i = 0; i < partialOrdering.size(); i++) {
				Triggerable[] edge = (Triggerable[])partialOrdering.elementAt(i);
				roots.removeElement(edge[1]);
			}
			
			//if no root nodes while graph still has nodes, graph has cycles
			if (roots.size() == 0) {
				String hints = "";
				for(Triggerable t : vertices) {
					for(TreeReference r : t.getTargets()) {
						hints += "\n" + r.toString(true);
					}
				}
				String message = "Cycle detected in form's relevant and calculation logic!";
				if(!hints.equals("")) {
					message += "\nThe following nodes are likely involved in the loop:" + hints;
				}
				throw new IllegalStateException(message);
			}

			//remove root nodes and edges originating from them
			for (int i = 0; i < roots.size(); i++) {
				Triggerable root = (Triggerable)roots.elementAt(i);
				triggerables.addElement(root);
				vertices.removeElement(root);
			}			
			for (int i = partialOrdering.size() - 1; i >= 0; i--) {
				Triggerable[] edge = (Triggerable[])partialOrdering.elementAt(i);
				if (roots.contains(edge[0]))
					partialOrdering.removeElementAt(i);
			}
		}
		
		triggerablesInOrder = true;
		
		//
		//build the condition index for repeatable nodes
		//
		
		conditionRepeatTargetIndex = new Hashtable();
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			if (t instanceof Condition) {
				Vector targets = t.getTargets();
				for (int j = 0; j < targets.size(); j++) {
					TreeReference target = (TreeReference) targets.elementAt(j);
					if (mainInstance.getTemplate(target) != null) {
						conditionRepeatTargetIndex.put(target, (Condition) t);
					}
				}
			}
		}

	}
		
	/**
	 * Get all of the elements which will need to be evaluated (in order) when the
	 * triggerable is fired.
	 * @param t
	 */
	public void fillTriggeredElements(Triggerable t, Vector<Triggerable> destination) {
		if (t.canCascade()) {
			for (int j = 0; j < t.getTargets().size(); j++) {
				TreeReference target = (TreeReference)t.getTargets().elementAt(j);
				Vector<TreeReference> updatedNodes = new Vector<TreeReference>();
				updatedNodes.addElement(target);
				
				//For certain types of triggerables, the update will affect not only the target, but
				//also the children of the target. In that case, we want to add all of those nodes 
				//to the list of updated elements as well.
				if(t.isCascadingToChildren()) {
					addChildrenOfReference(target, updatedNodes);
				}
				
				//Now go through each of these updated nodes (generally just 1 for a normal calculation,
				//multiple nodes if there's a relevance cascade.
				for(TreeReference ref : updatedNodes) {
					//Check our index to see if that target is a Trigger for other conditions
					//IE: if they are an element of a different calculation or relevancy calc
					
					//We can't make this reference generic before now or we'll lose the target information,
					//so we'll be more inclusive than needed and see if any of our triggers are keyed on
					//the predicate-less path of this ref
					Vector<Triggerable> triggered = (Vector<Triggerable>)triggerIndex.get(ref.hasPredicates() ? ref.removePredicates() : ref);
					
					if (triggered != null) {
						//If so, walk all of these triggerables that we found
						for (int k = 0; k < triggered.size(); k++) {
							Triggerable u = (Triggerable)triggered.elementAt(k);
							
							//And add them to the queue if they aren't there already
							if (!destination.contains(u))
								destination.addElement(u);
						}
					}
				}

			}
		}
	}





	public void initializeTriggerables() {
		initializeTriggerables(TreeReference.rootRef());
	}

	/**
	 * Walks the current set of conditions, and evaluates each of them with the
	 * current context.
	 */
	private void initializeTriggerables(TreeReference rootRef) {
		TreeReference genericRoot = rootRef.genericize();

		Vector applicable = new Vector();
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			for (int j = 0; j < t.getTargets().size(); j++) {
				TreeReference target = (TreeReference)t.getTargets().elementAt(j);
				if (genericRoot.isParentOf(target, false)) {
					applicable.addElement(t);
					break;
				}
			}
		}
		
		evaluateTriggerables(applicable, rootRef);
	}
	
	/**
	 * The entry point for the DAG cascade after a value is changed in the model.
	 * 
	 * @param ref The full contextualized unambiguous reference of the value that was
	 * changed.
	 */
	public void triggerTriggerables(TreeReference ref) {
		
		//turn unambiguous ref into a generic ref
		//to identify what nodes should be triggered by this 
		//reference changing
		TreeReference genericRef = ref.genericize();

		//get triggerables which are activated by the generic reference
		Vector triggered = (Vector)triggerIndex.get(genericRef);
		if (triggered == null) {
			return;
		}

		//Our vector doesn't have a shallow copy op, so make one 
		Vector triggeredCopy = new Vector();
		for (int i = 0; i < triggered.size(); i++) { 
			triggeredCopy.addElement(triggered.elementAt(i));
		}
		
		//Evaluate all of the triggerables in our new vector
		evaluateTriggerables(triggeredCopy, ref);
	}

	/**
	 * Step 2 in evaluating DAG computation updates from a value being changed in 
	 * the instance. This step is responsible for taking the root set of directly
	 * triggered conditions, identifying which conditions should further be triggered
	 * due to their update, and then dispatching all of the evaluations.
	 * 
	 * @param tv A vector of all of the trigerrables directly triggered by the 
	 * value changed
	 * @param anchorRef The reference to original value that was updated
	 */
	private void evaluateTriggerables(Vector tv, TreeReference anchorRef) {
		//add all cascaded triggerables to queue
		
		//Iterate through all of the currently known triggerables to be triggered
		for (int i = 0; i < tv.size(); i++) {
			Triggerable t = (Triggerable)tv.elementAt(i);
			fillTriggeredElements(t, tv);
		}
		
		//tv should now contain all of the triggerable components which are going to need to be addressed
		//by this update.
		//'triggerables' is topologically-ordered by dependencies, so evaluate the triggerables in 'tv'
		//in the order they appear in 'triggerables'
		for (int i = 0; i < triggerables.size(); i++) {
			Triggerable t = (Triggerable)triggerables.elementAt(i);
			if (tv.contains(t)) {
				evaluateTriggerable(t, anchorRef);
			}
		}
	}
	
	/**
	 * Step 3 in DAG cascade. evaluate the individual triggerable expressions against
	 * the anchor (the value that changed which triggered recomputation)
	 * 
	 * @param t The triggerable to be updated
	 * @param anchorRef The reference to the value which was changed.
	 */
	private void evaluateTriggerable(Triggerable t, TreeReference anchorRef) {
		
		//Contextualize the reference used by the triggerable against the anchor
		TreeReference contextRef = t.contextRef.contextualize(anchorRef);
		
		//Now identify all of the fully qualified nodes which this triggerable
		//updates. (Multiple nodes can be updated by the same trigger)
		Vector<TreeReference> v = exprEvalContext.expandReference(contextRef);
		
		//Go through each one and evaluate the trigger expresion
		for (int i = 0; i < v.size(); i++) {
			try {
				t.apply(mainInstance, exprEvalContext, v.elementAt(i), this);
			}catch(RuntimeException e) {
				throw e;
			}
		}
	}
	
	/**
	 * This is a utility method to get all of the references of a node. It can be replaced
	 * when we support dependent XPath Steps (IE: /path/to//)
	 */
	public void addChildrenOfReference(TreeReference original, Vector<TreeReference> toAdd) {
		for(TreeReference ref : exprEvalContext.expandReference(original)) {
			addChildrenOfElement(exprEvalContext.resolveReference(ref), toAdd);
		}
	}
	
	//Recursive step of utility method 
	private void addChildrenOfElement(AbstractTreeElement el, Vector<TreeReference> toAdd) {
		for(int i = 0 ; i < el.getNumChildren() ; ++i) {
			AbstractTreeElement child = el.getChildAt(i);
			toAdd.addElement(child.getRef().genericize());
			addChildrenOfElement(child, toAdd);
		}
		for(int i = 0; i < el.getAttributeCount() ; ++i) {
			AbstractTreeElement child = el.getAttribute(el.getAttributeNamespace(i), el.getAttributeName(i));
			toAdd.addElement(child.getRef().genericize());
		}
	}

	public boolean evaluateConstraint(TreeReference ref, IAnswerData data) {
{'fraction_non_alphanumeric': 0.10296961325966851, 'fraction_numerical': 0.0024689226519337016, 'mean_word_length': 3.903572637995259, 'pattern_counts': {'":': 0, '<': 138, '<?xml version=': 0, '>': 97, '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': '2970298', 'n_tokens_mistral': 19043, 'n_tokens_neox': 16293, 'n_words': 5207}