All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.xfa.form.FormInstanceManager Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*
 * ADOBE CONFIDENTIAL
 *
 * Copyright 2005 Adobe Systems Incorporated All Rights Reserved.
 *
 * NOTICE: All information contained herein is, and remains the property of
 * Adobe Systems Incorporated and its suppliers, if any. The intellectual and
 * technical concepts contained herein are proprietary to Adobe Systems
 * Incorporated and its suppliers and may be covered by U.S. and Foreign
 * Patents, patents in process, and are protected by trade secret or copyright
 * law. Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained from
 * Adobe Systems Incorporated.
 */
package com.adobe.xfa.form;

import com.adobe.xfa.AppModel;
import com.adobe.xfa.ScriptTable;
import com.adobe.xfa.ScriptHandler;
import com.adobe.xfa.XFAList;
import com.adobe.xfa.Int;
import com.adobe.xfa.ProtoableNode;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.Model;
import com.adobe.xfa.EnumAttr;
import com.adobe.xfa.Element;
import com.adobe.xfa.Node;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.XFA;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.template.containers.Container;
import com.adobe.xfa.template.containers.Subform;
import com.adobe.xfa.data.DataNode;

import java.util.ArrayList;
import java.util.List;

/**
 * @exclude from public api.
 */
public class FormInstanceManager extends ProtoableNode {

	private final List mInstances = new ArrayList();
	private ProtoableNode mTemplateNode;
	private boolean mbMatchDescendantsOnly;

	private InstanceListener mListener;

	// state member var, records when we trigger initialize event on newly added instance.
	private boolean mbInitializingNewInstance;

	public FormInstanceManager(Element parent, Node prevSibling) {
		super(parent, prevSibling, "", XFA.INSTANCEMANAGER, XFA.INSTANCEMANAGER, null, XFA.INSTANCEMANAGERTAG, XFA.INSTANCEMANAGER);
	}

//	// ~XFAFormInstanceManagerImpl() {
//	void dispose() {
//		mListener = null;
//		
//		mbInitializingNewInstance = false;
//		
//		// clear the instance manager for all nodes this manager controls.
//		for (int i = 0; i < mInstances.size(); i++) {
//			Object instance = mInstances.get(i);
//		 
//			if (instance instanceof FormSubform) {
//				// set the instanceManager for poInstance to null
//				FormSubform formSubform = (FormSubform)instance;
//				formSubform.setInstanceManager(null);
//			}
//			else if (instance instanceof FormSubformSet) {
//				// set the instanceManager for poInstance to null
//				FormSubformSet formSubformSet = (FormSubformSet)instance;
//				formSubformSet.setInstanceManager(null);
//			}
//		 }
//	 }

	/**
	 * Add or remove instances of an Subform or SubformSet child of this
	 * XFAForm node.
	 * 
	 * This method will automatically ensure that no occurrence or
	 * hierarchy rules are invalidated. During a remove data nodes are
	 * unmapped and during an add data is automatically merged into the
	 * new instances. This method will also recursively add/remove the
	 * children of the new node.
	 * 
	 * @param nNumber the number of instances of this subform that we want to have
	 * @param bThrow if true, an exception will be throw if nNumber
	 * is not a valid number of occurrences.
	 */
	public void setInstances(int nNumber, boolean bThrow /* = true */) {
		// get the max occurrence info
		int nMax = getMax();
		
		FormModel formModel = (FormModel)getModel();
		if (formModel.mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) {
			// V3.1 doesn't allow instance management in MATCH_TEMPLATE merge mode.  In future versions,
			// we'll likely implement some sort of incremental merge to correctly handle some of the edge
			// cases of instance management (such as creating an instance that another template node
			// would have bound to).
			if (bThrow)
				throw new ExFull(ResId.InstanceManagmentNotAllowed);
			else
				return;
		}

		// ensure the number given is valid
		if (nNumber < 0) {
			if (bThrow)
				throw new ExFull(new MsgFormatPos(ResId.OccurrenceViolationException, XFA.MIN));
			else
				return;
		}

		if ((nNumber > nMax) && (nMax != -1)) {
			if (bThrow)
				throw new ExFull(new MsgFormatPos(ResId.OccurrenceViolationException, XFA.MAX));
			else
				return;
		}			
		else if (nNumber < getMin()) {
			if (bThrow)
				throw new ExFull(new MsgFormatPos(ResId.OccurrenceViolationException, XFA.MIN));
			else
				return;
		}

		// if number is bigger than current number of instances create new ones.
		if (nNumber > getCount()) {

			// find where "this" occurs
			int nManagerIndex = getIndex();

			// the number to create
			nNumber = nNumber - getCount();

			for (int i = 0; i < nNumber; i++) {
				// will notify of index change
				addChild(nManagerIndex, getCount(), true);
			}
		}
		// else remove instances.
		else if (nNumber < getCount()) {
			// the number to remove
			nNumber = getCount() - nNumber;
			// remove
			for (int i = 0; i < nNumber; i++) {
				removeInstance(mInstances.size() - 1, false);
			}
		}

		// make all nodes non-default
		clearDefaults();

		// notify peers so dependency tracking can work
		notifyPeers(Peer.UPDATED, "", null);
	}

	/**
	 * Add an instance of a Subform or SubformSet to this Form node.
	 * 
	 * This method will automatically ensure that no occurrence or
	 * hierarchy rules are invalidated. This method will also
	 * recursively add the children of the new node.
	 * 
	 * @param bMerge if true attempt to merge the new Form node against the
	 *         XFA-Data DOM.
	 * @return a null Node if no node was added, else the newly created
	 *         Form node.

	 */
	public Node addInstance(boolean bMerge /* = true */) {
		return addInstance(getCount(), bMerge);
	}

	Node addInstance(int nInstance, boolean bMerge /* = true */) {
		// get the max occurrence info
		int nMax = getMax();
		Node bRet = null;
		
		FormModel formModel = (FormModel)getModel();
		if (formModel.mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) {
			// V3.1 doesn't allow instance management in MATCH_TEMPLATE merge mode.  In future versions,
			// we'll likely implement some sort of incremental merge to correctly handle some of the edge
			// cases of instance management (such as creating an instance that another template node
			// would have bound to).
			throw new ExFull(ResId.InstanceManagmentNotAllowed);
		}

		if (nInstance > getCount())
			throw new ExFull(ResId.IndexOutOfBoundsException);

		// ensure if we add a new instance do we respect the max occur info
		if ((nMax == -1) || ((int) nMax > getCount())) {
			// find where "this" occurs
			int nManagerIndex = getIndex();

			// this will send index Changed event
			bRet = addChild(nManagerIndex, nInstance, bMerge);
		} else {
			throw new ExFull(new MsgFormatPos(ResId.OccurrenceViolationException, XFA.MAX));
		}

		// make all nodes non-default
		clearDefaults();
		// send notification that calcs can fire
		notifyPeers(Peer.UPDATED, "", null);
		return bRet;
	}

	void addInstance(Node instance, boolean bValidate /* = false */) {
		
		// Note: while this routine appears to be part of instance management (and should therefore be protected by
		// a check for poFormModel->mergeMode() == XFAEnum::MERGEMODE_MATCHTEMPLATE, it's actually part of the merge
		// algorithm (it's simply adding newly-merged nodes to the instance manager, not creating new instances).
		
		if (bValidate) {
			int nMax = getMax();

			// if not infinity and already at max issue error.
			if ((nMax != -1) && ((int) nMax < getCount()))
				throw new ExFull(new MsgFormatPos(ResId.OccurrenceViolationException, XFA.MAX));
		}
		
		if (instance instanceof FormSubform) {
			// set the instanceManager for poInstance
			// and add a pointer to the poInstance to this
			FormSubform formSubformInstance = (FormSubform) instance;
			formSubformInstance.setInstanceManager(this);
			mInstances.add(formSubformInstance);
		} else if (instance instanceof FormSubformSet) {
			// set the instanceManager for poInstance
			// and add a pointer to the poInstance to this
			FormSubformSet formSubformSetInstance = (FormSubformSet) instance;
			formSubformSetInstance.setInstanceManager(this);
			mInstances.add(formSubformSetInstance);
		}
	}

	/**
	 * Remove a Subform or SubformSet from this Form node.
	 * 
	 * This method will automatically ensure that no occurrence rules
	 * are invalidated and the data is removed from the XFA-Data DOM.
	 * 
	 * @param nInstance the instance to remove.
	 */
	public void removeInstance(int nInstance, boolean bNotifyPeers /* = true */) {
		// ensure nInstance is valid
		if (nInstance >= getCount())
			throw new ExFull(ResId.IndexOutOfBoundsException);

		// ensure if we remove a node we don't violate the min occur info
		if ((getCount() - 1) < getMin())
			throw new ExFull(new MsgFormatPos(
					ResId.OccurrenceViolationException, XFA.MIN));
		
		FormModel formModel = (FormModel)getModel();
		if (formModel.mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) {
			// V3.1 doesn't allow instance management in MATCH_TEMPLATE merge mode.  In future versions,
			// we'll likely implement some sort of incremental merge to correctly handle some of the edge
			// cases of instance management (such as creating an instance that another template node
			// would have bound to).
			throw new ExFull(ResId.InstanceManagmentNotAllowed);
		}

		// get the instance
		Element instance = mInstances.get(nInstance);

		// notify a listener, if any that a node is about to be removed
		Node removedFormNode = instance;
		if (null != mListener) {
			mListener.preRemoveInstance(removedFormNode);
		}

		// force the removal any script object pointers to this form node
		// by default the script object will keep the node alive
		List handlers = getAppModel().getScriptHandlers();
		for (int i = 0; i < handlers.size(); i++) {
			handlers.get(i).removeReference(removedFormNode);
		}

		// removes the nodes
		
		// watson bug 1705701,   if this is a legacy doc once we find a data node
		// stop removing data nodes
		// watson bug 1440694  old 7.0 forms don't respect the override attribute, 
		// must continue to respect this
		boolean bLegacy = getAppModel().getLegacySetting(AppModel.XFA_LEGACY_V27_SCRIPTING); 
		
		cleanDataNodes(instance, bLegacy);  // this removes the data listeners and data nodes

		// null out the instance manager so this.cleanup(node isn't called);
		if (instance instanceof FormSubform) {
			// set the instanceManager for poInstance to null
			FormSubform formSubformInstance = (FormSubform) instance;
			formSubformInstance.setInstanceManager(null);
		} else if (instance instanceof FormSubformSet) {
			// set the instanceManager for poInstance to null
			FormSubformSet formSubformSetInstance = (FormSubformSet) instance;
			formSubformSetInstance.setInstanceManager(null);
		}

		// Watson 1101722- the next line will decrement ref count of poInstance,
		// causing crash when we pass it to listener.
		// So keep it around with the local variable oRemovedFormNode int enough to
		// inform any listener of the post-remove event
		instance.remove();
		mInstances.remove(nInstance);

		// notify a listener, if any, that a node was just removed
		if (null != mListener) {
			mListener.postRemoveInstance(removedFormNode);
		}

		if (bNotifyPeers) {
			// send index changed event to all affected nodes
			if (nInstance < getCount())
				broadcastIndexChange(nInstance, getCount() - 1, true);

			// make all nodes non-default
			clearDefaults();
			// notify peers so dependency tracking can work
			notifyPeers(Peer.UPDATED, "", null);
		}

		// ensure that all the children have a null model, this way we
		// can ensure that we don't run any calcs or validates on these
		// nodes, this also removes any peers and events tied to this node
		Model model = getModel();
		if (model != null)
			model.removeReferences(instance);
	}

	/**
	 * Move a child of this Form node and the corresponding data in the
	 * XFA-Data DOM.
	 * 
	 * This method will automatically ensure that no occurrence or
	 * hierarchy rules are invalidated.
	 * 
	 * @param nInstance the instance to be moved.
	 * @param nPosition the position of the child within its instances (0 based).
	*/
	public void moveInstance(int nInstance, int nPosition, boolean bNotify /* = true */) {
		
		FormModel formModel = (FormModel)getModel();
		if (formModel.mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) {
			// V3.1 doesn't allow instance management in MATCH_TEMPLATE merge mode.  In future versions,
			// we'll likely implement some sort of incremental merge to correctly handle some of the edge
			// cases of instance management (such as creating an instance that another template node
			// would have bound to).
			throw new ExFull(ResId.InstanceManagmentNotAllowed);
		}
		
		if (nInstance == nPosition)
			return;

		// invalid position
		if (getCount() < 2 || 
			nPosition < 0 || nPosition >= getCount() || 
			nInstance < 0 || nInstance >= getCount())
			throw new ExFull(ResId.IndexOutOfBoundsException);

		int nMax = getMax();

		// can't move;
		if (nMax == 0 || nMax == 1)
			throw new ExFull(new MsgFormatPos(ResId.OccurrenceViolationException, XFA.MAX));

		// get instance
		Element instance = mInstances.get(nInstance);

		// notify a listener, if any, that a node is just about to be moved
		Node movedFormNode = instance;
		if (null != mListener) {
			mListener.preMoveInstance(movedFormNode);
		}

		// remove the instance link
		mInstances.remove(nInstance);

		Element parent = getXFAParent();

		Node refNode = null;
		int nRefIndex;

		// try to get ref node form moInstances
		if (getCount() > nPosition) {
			refNode = mInstances.get(nPosition);
			// re-add the instance to the list
			mInstances.add(nPosition, instance);
		} else { // getCount() == nPosition
			nRefIndex = getIndex() + getCount() + 2;

			int nCount = 0;
			for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling(), nCount++) {
				if (nCount == nRefIndex) {
					refNode = child;
					break;
				}
			}

			// re-add the instance to the list
			mInstances.add(instance);
		}

		// if no reference node so just append
		if (refNode == null)
			parent.appendChild(instance, false);
		else
			parent.insertChild(instance, refNode, false);

		// notify a listener, if any, that a node was just moved
		if (null != mListener) {
			mListener.postMoveInstance(movedFormNode);
		}

		moveDataNodes(nInstance, nPosition);

		if (bNotify) {
			// watson bug 1791382,   for new docs only send the index changed event to nodes 
			// affected by the move opp.
			boolean bUseRange = !getAppModel().getLegacySetting(AppModel.XFA_LEGACY_V27_SCRIPTING);
			
			// send index changed event to the two affected nodes
			broadcastIndexChange(nInstance, nPosition, bUseRange);

			// make all nodes non-default
			clearDefaults();
			// notify peers so dependency tracking can work
			notifyPeers(Peer.UPDATED, "", null);
		}
	}
	
	// revert moveDataNodes to 7.0.8 codebase to ensure backwards compatibility
	private void addDataNodes(int nIndex) {
		
		// watson bug 1561530, don't move data that is bound to a schema since
		// the data creation code handles this.
//	#if 0
//		XFAFormModelImpl * poFormModel = (XFAFormModelImpl *)getModelImpl();
//
//		XFANodeImpl* poInstance	= moInstances[nIndex];
//
//		XFAEnum::VALUE eType = poFormModel->mergeType(poInstance);
//		if (eMergeType == XFAEnum::MATCH_ONCE || eMergeType == XFAEnum::MATCH_DESCENDANT)
//		{
//		}
//
//		getDataNodes(poInstance,oDataNodes);
//
//	#endif
	}

	/**
	 * Interface for being notified of instances being added/removed/moved
	 *
	 * @exclude from public api.
	 */
	public interface InstanceListener {

		// Notification just before instance is added/removed/moved
		void preAddInstance();

		void preRemoveInstance(Node toBeRemoved);

		void preMoveInstance(Node toBeMoved);

		// Notification just after instance is added/removed/moved
		void postAddInstance(Node wasAdded);

		void postRemoveInstance(Node wasRemoved);

		void postMoveInstance(Node wasMoved);
	}

	/**
	 * Register an instance listener to this manager. This listener will be
	 * notified of instances being added/removed/moved
	 */
	public void setInstanceListener(InstanceListener listener) {
		mListener = listener;
	}

	void cleanup(Node node) {
		// safety first: remove this subform from the manager
		// note: the manager should be freed prior to the destruction of any
		// instance 99% of the time
		for (int i = 0; i < mInstances.size(); i++) {
			Element poInstance = mInstances.get(i);
			if (node == poInstance) {
				mInstances.remove(i);
				break;
			}
		}
	}

	int getCount() {
		return mInstances.size();
	}

	int getMin() {
		// get the min occurrence setting
		Element occur = getElement(XFA.OCCURTAG, 0);
		Int min = (Int)occur.getAttribute(XFA.MINTAG);
		return min.getValue();
	}

	int getMax() {
		// get the max occurrence setting
		Element occur = getElement(XFA.OCCURTAG, 0);
		Int max = (Int)occur.getAttribute(XFA.MAXTAG);
		return max.getValue();
	}

	void setTemplateNode(ProtoableNode templateNode) {
		// construct the name for this instanceManager
		String aName = templateNode.getName();
		aName = aName == "" ? "_" : ("_" + aName).intern();
		privateSetName(aName);

		mTemplateNode = templateNode;
		
		ProtoableNode occur = (ProtoableNode)templateNode.getElement(XFA.OCCURTAG, true, 0, false, false);

		// copy the occur element over
		if (occur != null) {
			occur.createProto(this, false);
		}
	}

	int findIndex(Node instance) {
		for (int i = 0; i < mInstances.size(); i++) {
			if (mInstances.get(i) == instance)
				return i;
		}
		return 0;
	}

	private void clearDefaults() {
		for (int i = 0; i < mInstances.size(); i++) {
			mInstances.get(i).makeNonDefault(false);
		}
	}

	private Node addChild(int nManagerIndex, int nInsertIndex, boolean bMerge) {
		// Watson 1124534: Infinite loop caused when subform initialize event uses instance manger to create another instance of itself.
		// Well the new instance was added already, but we avoid adding new instance if we detect this case
		// Granted this is just bad form design but we'll protect the user from themselves.
		if (mbInitializingNewInstance) {
			// bad form design - warn of recursive or cyclic initialize event scripts
			MsgFormatPos formatPos = new MsgFormatPos(ResId.XFAAddInstanceRecursiveException);
			Node node = getTemplateNode();
			if (null != node)
				formatPos.format(node.getName());
			throw new ExFull(formatPos);
		}
		
		FormModel formModel = (FormModel)getModel();
		
		// get the parent node
		Element parent = getXFAParent();

		// notify a listener, if any that instance is about to be added
		if (null != mListener) {
			mListener.preAddInstance();
		}

		Node mappedParent;
		if (parent.isMapped())
			mappedParent = parent;
		else
			mappedParent = FormModel.getMappedParent(parent);

		boolean bMatchDescendantsOnly = formModel.getMatchDescendantsOnly();
		formModel.setMatchDescendantsOnly(getMatchDescendantsOnly());
		// TODO calc if in ExplictScope

		DataNode dataParent = FormModel.getDataNode(mappedParent);
		
		Element newFormNode = null;

		formModel.setAllowNewNodes(true);

		if (!bMerge) {
			// load empty form dom
			formModel.setEmptyMerge(true);

			// A CONSUME_DATA merge uses a second pass for data creation so all we need to do here
			// is create empty form nodes.
			// A MATCH_TEMPLATE merge does everything in a single pass, which happens below in the
			// "if (poNewFormNode == NULL)" block.
			//
			if (formModel.mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA)
				newFormNode = formModel.createEmptyFormNode(getTemplateNode(), parent, null, this);

		} 
		else {
			// try to merge data that exists in the data dom.
			formModel.setEmptyMerge(false);

			if (getTemplateNode() instanceof Subform) {
				Container.FormInfo info = formModel.getFormInfo(getTemplateNode(), dataParent, null);
				DataNode matched = formModel.findMatch(info, true, FormModel.DatasetSelector.MAIN_DATASET);
				if (matched != null) {
					newFormNode = formModel.createFormNode(getTemplateNode(), parent, this);
					formModel.bindNodes(newFormNode, matched, false);
					formModel.consumeDataNode(null, matched, FormModel.DatasetSelector.MAIN_DATASET);
					formModel.createAndMatchChildren(getTemplateNode(), matched, newFormNode, null);				
				} 
				else if (info != null && 
						 (info.eMergeType == EnumAttr.MATCH_GLOBAL || 
						  info.eMergeType == EnumAttr.MATCH_DATAREF)) {
					
					// A CONSUME_DATA merge uses a second pass for data creation so all we need to 
					// do here is create empty form nodes.
					// A MATCH_TEMPLATE merge does everything in a single pass, which happens below 
					// in the "if (poNewFormNode == NULL)" block.
					//
					if (formModel.mergeMode() == EnumAttr.MERGEMODE_CONSUMEDATA)
						newFormNode = formModel.createEmptyFormNode(getTemplateNode(), parent, null, this);
				}
			}

			if (newFormNode == null) {
				newFormNode = formModel.createFormNode(getTemplateNode(), parent, this);
				if (formModel.mergeMode() == EnumAttr.MERGEMODE_MATCHTEMPLATE) {
					int eMergeType = formModel.mergeType(getTemplateNode(), "", null);
					DataNode dataNode = formModel.createDataNode(newFormNode, dataParent, eMergeType, true);
					formModel.bindNodes(newFormNode, dataNode, true);
					formModel.consumeDataNode(null, dataNode, FormModel.DatasetSelector.MAIN_DATASET);
					dataParent = dataNode;
				}
				formModel.createAndMatchChildren(getTemplateNode(), dataParent, newFormNode, null);
			}
		}

		formModel.setAllowNewNodes(false);

		// move node into correct position
		Node refNode = null;

		int nRefIndex = nManagerIndex + getCount();

		if (parent.getXFAChildCount() > nRefIndex + 1)
			refNode = parent.getXFAChild(nRefIndex);

		if (refNode != null)
			parent.insertChild(newFormNode, refNode, true);

		formModel.mergeSecondPass(parent, dataParent);

		// watson 1432253 set dynamic properties follows mergeSecondPass as in normal
		// merge case
		if (newFormNode instanceof Container)
			formModel.setDynamicProperties((Container) newFormNode, "", true);

		// watson bug 1222926 ensure the new data node is in the correct place
		addDataNodes(nInsertIndex);

		// reset
		formModel.setMatchDescendantsOnly(bMatchDescendantsOnly);

		// notify a listener, if any that instance was just added
		if (null != mListener) {
			Node oNewFormNode = newFormNode;
			mListener.postAddInstance(oNewFormNode);
		}

		// move node into correct position
		moveInstance(getCount() - 1, nInsertIndex, false);

		// Watson 1124534: Infinite loop caused when subform initialize event
		// uses instance manger to create another instance of itself.
		// Well the new instance was added already, but we can avoid calling
		// initialize event again if we detect this case/
		// Granted this is just bad form design but we'll protect the user from
		// themselves.
		assert (!mbInitializingNewInstance);
		if (!mbInitializingNewInstance) {
			// Set flag indicating we are calling initialize on new instance.
			mbInitializingNewInstance = true;
			try {
				String sInitialize = EnumAttr.getString(EnumAttr.ACTIVITY_INITIALIZE);
				formModel.eventOccurred(sInitialize, newFormNode);
			} finally {
				mbInitializingNewInstance = false;
			}
		}

		// send index changed event to the two affected nodes
		if (getCount() > 0)	// Watson 1894170 -- ensure that script associated with ACTIVITY_INITIALIZE event didn't remove instance.
			broadcastIndexChange(getCount() - 1, nInsertIndex, true);

		assert (!mbInitializingNewInstance);

		return newFormNode;
	}

	private static void insertNodeAfter(Node newNode, Node prevRefNode) {
		// insert newNode after prevRefNode
		Element parent = prevRefNode.getXFAParent();
		if (parent == null)
			throw new ExFull(ResId.InsertFailedException);

		Node refNode = prevRefNode.getNextXFASibling();
		if (refNode != null) {
			// only insert the child if needed
			if (newNode != refNode)
				parent.insertChild(newNode, refNode, true);
		} else
			parent.appendChild(newNode, true);
	}

	// revert moveDataNodes to 7.0.8 codebase to ensure backwards compatibility
	@FindBugsSuppress(code="ES")
	private void moveDataNodes(int nFrom, int nTo) {
		// MoveDataNodes
		Element instance = mInstances.get(nTo);
		//Node poMappedParent = FormModel.getMappedParent(instance);
		//Node poDataParent = FormModel.getDataNode(poMappedParent);

		List dataNodes = new ArrayList();
		List prevRefDataNodes = new ArrayList();
		List postRefDataNodes = new ArrayList();

		getDataNodes(instance, dataNodes);

		final int nNodeSet = dataNodes.size();

		boolean bGiveWarning = false;

		// verify and move data
		if (nTo > 0) {
			// previous instance
			Element refNode = mInstances.get(nTo - 1);

			getDataNodes(refNode, prevRefDataNodes);

			final int nPrevSet = prevRefDataNodes.size();

			// if both have 1 data node, all is ok, just move the node
			if (nNodeSet == nPrevSet && 
				nNodeSet == 1) {
				
				insertNodeAfter(dataNodes.get(0), prevRefDataNodes.get(0));
				return;
			}

			// the preceding instance has less data nodes, assume that it isn't round trippable
			if (nNodeSet > nPrevSet)
				bGiveWarning = true;

			boolean bFound = false;

			for (int i = nNodeSet; i > 0; i--) {
				Element node = dataNodes.get(i - 1);
				for (int j = nPrevSet; j > 0; j--) {
					Element node2 = prevRefDataNodes.get(j - 1);
					if (node2.isSameClass(node) && 
						node.getName() == node2.getName()) {

						bFound = true;

						// insert pNode after pNode2
						insertNodeAfter(node, node2);
						break;
					}
				}
				// no exact match so can't round trip
				if (!bFound)
					bGiveWarning = true;
			}
		}
			
		// verify that the next instance is valid, move data if we are the first instance
		if (nTo + 1 < getCount()) {
			
			// next instance
			Element refNode = mInstances.get(nTo + 1);

			getDataNodes(refNode, postRefDataNodes);

			final int nPostSet = postRefDataNodes.size();

			// if both have 1 data node, all is ok, just move the node
			if (nNodeSet == nPostSet && 
				nNodeSet == 1 && 
				nTo == 0) { // moved the node to the first position so weed to move the data here

				postRefDataNodes.get(0).getXFAParent().insertChild(
						dataNodes.get(0), 
						postRefDataNodes.get(0), true);
				return;
			}

			// the proceeding instance has more data nodes, assume that it isn't round trippable
			if (nNodeSet < nPostSet)
				bGiveWarning = true;

			for (int i = 0; i < nNodeSet; i++) {
				Element node = dataNodes.get(i);

				for (int j = 0; j < nPostSet; j++) {
					Element node2 = postRefDataNodes.get(j);
					if (node2.isSameClass(node) && 
						node.getName() == node2.getName()) {

						if (nTo == 0) {
							// insert node before node2
							node.getXFAParent().insertChild(node, node2, true);
							break;
						}
					}
				}
			}
		}

		if (bGiveWarning) {
			// warning that data won't roundtrip
			MsgFormatPos msgFormatPos = new MsgFormatPos(ResId.XFAMoveInstanceException);
			msgFormatPos.format(instance.getClassAtom());
			msgFormatPos.format(instance.getName());
			msgFormatPos.format(Integer.toString(nFrom));
			msgFormatPos.format(Integer.toString(nTo));
			throw new ExFull(msgFormatPos);
		}
	}

	private void getDataNodes(Node formNode, List dataNodes) {
		boolean bGetChildren = false;
		DataNode dataNode = null;

		if (formNode instanceof FormSubform) {
			bGetChildren = true;
			dataNode = ((FormSubform)formNode).getDataNode();
		} else if (formNode instanceof FormSubformSet) {
			bGetChildren = true;
		} else if (formNode instanceof FormField) {
			dataNode = ((FormField) formNode).getDataNode();
		} else if (formNode instanceof FormExclGroup) {
			bGetChildren = true;
			dataNode = ((FormExclGroup) formNode).getDataNode();
		}

		// add data node to list
		// don't add orphaned data nodes (watson bug 1335755) this cause a crash when we try to insert
		// nodes before or after this data node
		if (dataNode != null) {
			if (dataNode.getXFAParent() != null)
				dataNodes.add(dataNode);
			return;
		}

		// get data nodes that are peered to the children of the subformset
		if (bGetChildren) {
			for (Node childNode = formNode.getFirstXFAChild(); childNode != null; childNode = childNode.getNextXFASibling()) {
				if (childNode instanceof Element)
					getDataNodes((Element)childNode, dataNodes);
			}
		}
	}

	// Obj newClass(Element pParent, Model pModel, Node peer, int /*eClassTag*/, jfObjType) {
	// return new FormInstanceManager(pParent, peer, pModel);
	// }

	private int getIndex() {
		// get the parent node
		Element parent = getXFAParent();

		// find index of the template node in the form dom
		int index = 0;
		for (Node child = parent.getFirstXFAChild(); child != null; child = child.getNextXFASibling(), index++) {
			if (child == this)
				break;
		}
		return index;
	}

	private void broadcastIndexChange(int nStart, int nEnd, boolean bRange) {
		FormModel formModel = (FormModel) getModel();
		String sIndexChange = EnumAttr.getString(EnumAttr.ACTIVITY_INDEXCHANGE);
		if (bRange) {
			int nMin = Math.min(nStart, nEnd);
			int nMax = Math.max(nStart, nEnd);
			for (int i = nMin; i <= nMax; i++) {
				formModel.eventOccurred(sIndexChange, mInstances.get(i));
			}
		} else {
			formModel.eventOccurred(sIndexChange, mInstances.get(nStart));
			if (nStart != nEnd)
				formModel.eventOccurred(sIndexChange, mInstances.get(nEnd));
		}
	}

	// only if all nodes are global or not bound
	// can we restore the number of instances.
	private boolean canRestoreCount(Element node, FormModel model) {
		int eRetValue = model.mergeType(node, "", null);

		if (eRetValue != EnumAttr.MATCH_NONE && 
			eRetValue != EnumAttr.MATCH_GLOBAL)
			return false;

		if (node.isContainer()) {
			for (Node childNode = node.getFirstXFAChild(); childNode != null; childNode = childNode.getNextXFASibling()) {
				if (childNode instanceof Element)
					if (!canRestoreCount((Element)childNode, model))
						return false;
			}
		}
		return true;
	}
	
	private void cleanDataNodes(Node formNode, boolean bLegacyMode) {
		boolean bGetChildren = false;
		DataNode dataNode = null;
		if ( formNode instanceof FormSubform) {
			bGetChildren = true;
			FormSubform formSubform = (FormSubform)formNode;
			dataNode = formSubform.getDataNode();
			formSubform.cleanupListeners();
		}
		else if (formNode instanceof FormSubformSet) {
			bGetChildren = true;
		}
		else if (formNode instanceof FormField) {
			FormField formField = (FormField)formNode;
			dataNode = formField.getDataNode();
			formField.cleanupListeners();
		}
		else if (formNode instanceof FormExclGroup) {
			bGetChildren = true;
			FormExclGroup formExclGroup = (FormExclGroup)formNode;
			dataNode = formExclGroup.getDataNode();
			formExclGroup.cleanupListeners();
		}

		// add data node to list
		// don't add orphaned data nodes (watson bug 1335755)  this cause a crash when we try to insert 
		// nodes before or after this data node
		// Note: Don't remove orphaned DataNodes as they will cause an exception.
		if (dataNode != null && dataNode.getXFAParent() != null) {
			
			int nPeer = 0;
			int nListenerCount = 0;
			Peer peer = dataNode.getPeer(nPeer);
			while (peer != null) {
				
				if (peer instanceof FormDataListener)
					nListenerCount++;
				nPeer++;
				peer = dataNode.getPeer(nPeer);
			}

			// only remove the data node if there are no more data listeners
			// note getDataNodes will remove the current data listener
			if (nListenerCount == 0)
				dataNode.remove();
		
			// watson bug 1705701,   if this is a legacy doc once we find a data node
			// stop removing data nodes
			if (bLegacyMode)
				return;
		}

		// get data nodes that are peered to the children of the subformset
		if (bGetChildren) {			
			for (Node child = formNode.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
				cleanDataNodes(child, bLegacyMode);
			}
		}
	}

	// Adobe patent application tracking # B252, entitled METHOD AND SYSTEM TO PERSIST STATE, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
	// Adobe patent application tracking # B322, entitled METHOD AND SYSTEM TO MAINTAIN THE INTEGRITY OF A CERTIFIED DOCUMENT WHILE PERSISTING STATE IN A DYNAMIC FORM, inventors: Roberto Perelman, Chris Solc, Anatole Matveief, Jeff Young, John Brinkman
	public void getDeltas(Element delta, XFAList list) {
		
		if (!isSameClass(delta))
			return;

		FormModel model = (FormModel)getModel();

		// restore the value of the field if we are loading
		if (model.isLoading()) {
			
			FormInstanceManager deltaManager = (FormInstanceManager)delta;
			ProtoableNode templateNode = mTemplateNode;

			// calculate the number instances specified by the deltas
			int nNewCount = 0;
			int nStartIndex = deltaManager.getIndex() + 1;
			Element deltaParent = deltaManager.getXFAParent();

			// find the deltaChild at position nStartIndex 
			Node deltaChild = deltaParent.getFirstXFAChild();
			for (int nChildIndex = 0; deltaChild != null; deltaChild = deltaChild.getNextXFASibling(), nChildIndex++)
				if (nChildIndex == nStartIndex)
					break;
			
			// loop through the deltas and count the number of instances
			for (; deltaChild != null; deltaChild = deltaChild.getNextXFASibling()) {
				
				if (deltaChild.isSameClass(XFA.INSTANCEMANAGERTAG))
					break;

				if (deltaChild.isSameClass(templateNode))
					nNewCount++;
			}
			
			// watson bug 1565403
			// first restore the occurrence settings if we are allowed to automatically 
			// restore the values.  If the list is not null we know merge restoreState == auto
			// if we don't we can get an exception
			if (list != null && delta instanceof Element) {
				
				Element deltaOccur = ((Element)delta).getElement(XFA.OCCURTAG, true, 0, false, false);
				if (deltaOccur != null) {
					
					Element thisOccur = getElement(XFA.OCCURTAG, 0);

					Int value = (Int)deltaOccur.getAttribute(XFA.MINTAG, true, false);
					if (value != null)
						thisOccur.setAttribute(value, XFA.MINTAG);
					value = (Int)deltaOccur.getAttribute(XFA.MAXTAG, true, false);
					if (value != null)
						thisOccur.setAttribute(value, XFA.MAXTAG);
					value = (Int)deltaOccur.getAttribute(XFA.INITIALTAG, true, false);
					if (value != null)
						thisOccur.setAttribute(value, XFA.INITIALTAG);
				}
			}

			// if there are more/less instances try to add them
			if (nNewCount != getCount() && canRestoreCount(templateNode, model)) {
				// watson bug 1565403 don't throw an error if the new count violates min/max occurrence settings
				// because an exception will cause us to corrupt the form data
				setInstances(nNewCount, false);
			}
		}

		super.getDeltas(delta, list);
	}

	
	/** @exclude from published api. */
	boolean instanceManagerPermsCheck() {
	    //	If an instance manager or any of its ancestors is/are locked, you can't addInstance, insertInstance, 
	    //	moveInstance, removeInstance, setInstances.
	        
		// Javaport: Redundant - checked by checkAncestorPerms
//	    if (! checkPerms())
//	        return false;
	    
	    if (! checkAncestorPerms())
	        return false;
	    
	    //	If any of an instance manager's instances or instances' descendents is locked, you can't addInstance, insertInstance, 
	    //	moveInstance, removeInstance, setInstances.
	    
	    for (int nIndex = 0; nIndex < mInstances.size(); nIndex++) {
	        Element instance = mInstances.get(nIndex);
	        if (instance != null) {
	        	// Javaport: Redundant - checked by checkDescendentPerms
//	            if (! instance.checkPerms())
//	                return false;
	            
	            if (! instance.checkDescendentPerms())
	                return false;
	        }
	    }

	    return true;
	}

	/**
	 * @exclude from published api.
	 */
	public ScriptTable getScriptTable() {
		return FormInstanceManagerScript.getScriptTable();
	}

	private ProtoableNode getTemplateNode() {
		return mTemplateNode;
	}
	
	public void setMatchDescendantsOnly(boolean bDescendantsOnly) {
		mbMatchDescendantsOnly = bDescendantsOnly;
	}
	
	public boolean getMatchDescendantsOnly() {
		return mbMatchDescendantsOnly;		
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy