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

com.adobe.xfa.template.containers.Container 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.template.containers;

import org.xml.sax.Attributes;

import com.adobe.xfa.Attribute;
import com.adobe.xfa.StringAttr;
import com.adobe.xfa.Element;
import com.adobe.xfa.EnumAttr;
import com.adobe.xfa.Measurement;
import com.adobe.xfa.Node;
import com.adobe.xfa.NodeList;
import com.adobe.xfa.ProtoableNode;
import com.adobe.xfa.ScriptTable;
import com.adobe.xfa.XFA;
import com.adobe.xfa.form.FormExclGroup;
import com.adobe.xfa.template.automation.EventTag;
import com.adobe.xfa.ut.Assertions;
import com.adobe.xfa.ut.StringUtils;
import com.adobe.xfa.ut.UnitSpan;


/**
 * A base class to represent all XFA objects that are containers.
 */
public class Container extends ProtoableNode {
	
	/**
	 * @exclude from public api.
	 */
	public static class FormInfo {
		
		/**
		 * maintain a link to the template node so it can be cleared
		 */
		public final Container templateContainerNode;
		
		/**
		 * the data parent
		 */
		public Element dataParent;
		
		/**
		 * the connection data parent - needed for hasDescendantMatch
		 */
		public Element connectionDataParent;

		public Node scopeData;
		
		/**
		 * a list of data nodes
		 */
		public NodeList /*  */ dataNodes;
		
		/**
		 * indicates the dataNode list is from an association
		 */
		public boolean bAssociation;
		
		/**
		 * if eMergeType == MATCH_DATAREF, specifies that it's a connection DataRef
		 */
		public boolean bConnectDataRef;

		/**
		 * Normally bound nodes and data ref nodes that have * wild cards or predicates can only be used once
		 */
		public boolean bRemoveAfterUse;
		
		/**
		 * the merge type of the node
		 */
		public final int eMergeType;

		/**
		 * used who store connection data context for use by complex binding
		 */
		public final Element connectionDataNode;
		
		/**
		 * alternate list of data nodes
		 * (used only during a connection remerge - oDataNodes will contain connectionData nodes for merging
		 * while oAltDataNodes will contain existing data nodes. The merge will create form nodes for connection 
		 * data being imported by matching against the connection data nodes in oDataNodes, the merge 
		 * process will then use the alternate list to find match existing data nodes which are then 
		 * bound to the new form nodes and the data in the form nodes is used to update the existing data nodes)		 
		 */
		public NodeList /*  */ altDataNodes;
		
		
		/**
		 * This constructor is used during a "normal" merge.
		 */
		public FormInfo(Container templateContainerNode, int eMergeType) {
			this.templateContainerNode = templateContainerNode;
			this.eMergeType = eMergeType;
			
			connectionDataNode = null;
		}
		
		/**
		 * This constructor is used during a connection import. 
		 */
		public FormInfo(Element connectionDataNode) {
			templateContainerNode = null;
			eMergeType = EnumAttr.UNDEFINED;
			
			this.connectionDataNode = connectionDataNode;
		}
	}

	FormInfo mFormInfo;
	
	/**
	 * @exclude from published api.
	 */
	public enum ValidationState {
		VALIDATIONSTATE_VALID,
		VALIDATIONSTATE_BARCODETEST,
		VALIDATIONSTATE_NULLTEST,
		VALIDATIONSTATE_SCRIPTTEST,
		VALIDATIONSTATE_FORMATTEST
	}
	
	private static final int CHILD_INDEX_CACHE_SIZE = 5;	
	private final Node[] mChildIndexCache = new Node[CHILD_INDEX_CACHE_SIZE];
	
	private String msErrorText = "";
	private ValidationState meValidationState = ValidationState.VALIDATIONSTATE_VALID;
	
	private boolean mbDelayCacheRefresh;


	/**
	 * @exclude from published api.
	 */
	public Container() {
	}

	/**
	 * @exclude from published api.
	 */
	protected Container(Element parent, Node prevSibling, String uri,
			String localName, String qName, Attributes attributes,
			int classTag, String className) {
		super(parent, prevSibling, uri, localName, qName, attributes, classTag,
				className);
	}

	boolean checkProto(int eTag) {
		if (eTag == XFA.HTAG) {
			if ( (isSpecified(XFA.MINTAG, Element.ATTRIBUTE, false, 0) || isSpecified(XFA.MAXHTAG, Element.ATTRIBUTE, false, 0)) && 
				   isHeightGrowSupported())
				return false;
		}
		// if minH or maxH, and there exists a h don't look at proto
		else if (eTag == XFA.MINHTAG || eTag == XFA.MAXHTAG) {
			if (isSpecified(XFA.HTAG, Element.ATTRIBUTE, false, 0) && isHeightGrowSupported())
				return false;
		}
		// if w, and there exists a minW or maxW that exists, don't look at proto
		else if (eTag == XFA.WTAG) {
			if ((isSpecified(XFA.MINWTAG, Element.ATTRIBUTE, false, 0) || isSpecified(XFA.MAXWTAG, Element.ATTRIBUTE, false, 0)) && 
				   isWidthGrowSupported())
				return false;
		}
		// if minW or maxW, and there exists a w don't look at proto
		else if (eTag == XFA.MINWTAG || eTag == XFA.MAXWTAG) {
			if (isSpecified(XFA.WTAG, Element.ATTRIBUTE, false, 0) && isWidthGrowSupported())
				return false;
		}

		// default true
		return true;
	}
	
	/**
	 * @exclude from published api.
	 */
	public Attribute getAttribute(int eTag, boolean bPeek /* =false */,
			boolean bValidate/* =false */) {
		if (checkProto(eTag))
			return super.getAttribute(eTag, bPeek, bValidate); // Should use
		// ProtoableNode.getAttribute
		else {
			return elementGetAttribute(eTag, bPeek, false);
		}
	}

	/**
	 * Get the Event for the specified activity (OnEnter, etc.) Also returns
	 * pseudo-events (calculate and validate elements).
	 * 
	 * @param sActivity - The name of the activity
	 * @param sRef SOM expression to the referenced object
	 * @return an Event
	 */
	EventTag getEvent(String sActivity, String sRef) {
		boolean bIsPseudoEvent = sActivity.equals(XFA.CALCULATE) || sActivity.equals(XFA.VALIDATE);

		for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
			if (bIsPseudoEvent) {
				if (child.getClassAtom().equals(sActivity))
					return (EventTag) child;
			} else if (child.getClassAtom().equals(XFA.EVENT)) {
				EventTag event = (EventTag) child;
				if (!event.getActivity().equals(sActivity))
					continue;

				String sDefaultedRef = sRef;
				if (StringUtils.isEmpty(sDefaultedRef))
					sDefaultedRef = "$";
				if (!event.getRef().equals(sDefaultedRef))
					continue;

				return event;
			}
		}
		return null;

	}

	/**
	 * @exclude from public api.
	 */
	public FormInfo getFormInfo() {
		return mFormInfo;
	}

	/**
	 * Return an int for this containers presence property. Values will be one
	 * of visible, invisible or hidden. This method will check it's parent
	 * hierarchy to determine the correct presence.
	 * 
	 * @return The corresponding int.
	 *
	 * @exclude from published api.
	 */
	public int getRuntimePresence(int eParentPresence /* = EnumAttr.UNDEFINED */) {
		// Use a scoring mechanism to determine which presence value takes
		// precedence. Hidden has the highest score and will override any
		// other presence value, followed by invisible and visible.
		if (isSameClass(XFA.SUBFORMTAG) || isSameClass(XFA.FIELDTAG)
				|| isSameClass(XFA.EXCLGROUPTAG) || isSameClass(XFA.DRAWTAG)) {
			int nScore = 0;
			int nParentScore = 0;

			// Get this containers presence
			int ePresence = getEnum(XFA.PRESENCETAG);
			if (ePresence == EnumAttr.PRESENCE_INVISIBLE)
				nScore = 1;
			else if (ePresence == EnumAttr.PRESENCE_HIDDEN || ePresence == EnumAttr.PRESENCE_INACTIVE)
				nScore = 2;

			// Get our parent's presence (if not passed in)
			if (eParentPresence == EnumAttr.UNDEFINED) {
				Node oParent = getXFAParent();
				if (oParent instanceof Container)
					eParentPresence = ((Container)oParent).getRuntimePresence(EnumAttr.UNDEFINED);
			}
			
			if (eParentPresence == EnumAttr.PRESENCE_INVISIBLE)
				nParentScore = 1;
			else if (eParentPresence == EnumAttr.PRESENCE_HIDDEN || eParentPresence == EnumAttr.PRESENCE_INACTIVE)
				nParentScore = 2;

			// Return the presence with the greater score.
			if (nParentScore > nScore)
				return eParentPresence;
			else
				return ePresence;
		}
		else if (isSameClass(XFA.AREATAG) || (isSameClass(XFA.SUBFORMSETTAG) /* && LegacyFlag? */)) {
			//
			// Area and SubformSet don't have presence attributes.  Look to parent.
			//
			if (eParentPresence != EnumAttr.UNDEFINED)
				return eParentPresence;
			else {
				Container oParent = (Container)getXFAParent();
				return oParent.getRuntimePresence(EnumAttr.UNDEFINED);			
			}
		}	
		else if (isValidAttr(XFA.PRESENCETAG, false, null))
			return getEnum(XFA.PRESENCETAG);
		else
			// Probably a ContentArea
			return EnumAttr.PRESENCE_VISIBLE;
	}
	
	/**
	 * @exclude from published api.
	 */
	public int getRuntimeAccess(int eParentAccess /* = EnumAttr.UNDEFINED */) {
		// use a scoring system -- nonInteractive = 3, protected = 2, readOnly = 1, open = 0
		int nScore = 0;
		int nParentScore = 0;

		// does this container support the access property?
		int eAccess = EnumAttr.ACCESS_OPEN;	// initialize to "open"
		if (isValidAttr(XFA.ACCESSTAG, false, null)) {
			
			// get this container's access
			eAccess = getEnum(XFA.ACCESSTAG);
			if (eAccess == EnumAttr.ACCESS_NONINTERACTIVE)
				nScore = 3;
			else if (eAccess == EnumAttr.ACCESS_PROTECTED)
				nScore = 2;
			else if (eAccess == EnumAttr.ACCESS_READONLY)
				nScore = 1;
		}
		
		// Get our parent's access (if not passed in)
		if (eParentAccess == EnumAttr.UNDEFINED) {
			Node oParent = getXFAParent();
			if (oParent instanceof Container)
				eParentAccess = ((Container)oParent).getRuntimeAccess(EnumAttr.UNDEFINED);
		}
		if (eParentAccess == EnumAttr.ACCESS_NONINTERACTIVE)
			nParentScore = 3;
		else if (eParentAccess == EnumAttr.ACCESS_PROTECTED)
			nParentScore = 2;
		else if (eParentAccess == EnumAttr.ACCESS_READONLY)
			nParentScore = 1;
		
		// return the access with the greater score
		if (nParentScore > nScore)
			return eParentAccess;
		else
			return eAccess;
	}

	/**
	 * @exclude from published api.
	 */
	public ScriptTable getScriptTable() {
		return ContainerScript.getScriptTable();
	}
	
	/**
	 * The errorText property contains the validation error message for the
	 * last validation that failed. This only applies to the FormField,
	 * FormSubform, and FormExclGroup.
	 * @exclude from published api.
	 */
	public String getErrorText() {
		return msErrorText;
	}
	
	/**
	 * @exclude from published api.
	 */
	public void setErrorText(String sErrorText) {
		msErrorText = sErrorText;
	}
	
	/**
	 * @exclude from published api.
	 */
	public ValidationState getValidationState() {
		return meValidationState;
	}
	
	/**
	 * @exclude from published api.
	 */
	public void setValidationState(Container.ValidationState eValidationState) {
		meValidationState = eValidationState;
	}
	
	/**
	 * @exclude from published api.
	 */
	public void getInvalidObjects(NodeList invalidObjects) {
		for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
			if (!(child instanceof Container))
				continue;
			
			Container container = (Container)child;

			assert StringUtils.isEmpty(container.getErrorText()) == (container.getValidationState() == ValidationState.VALIDATIONSTATE_VALID);

			if (container instanceof Field ||
				container instanceof Subform ||
				container instanceof FormExclGroup) {
				
				if (container.getValidationState() != ValidationState.VALIDATIONSTATE_VALID) {
					invalidObjects.append(container);
				}
			}

			container.getInvalidObjects(invalidObjects);
		}
	}
	
    /**
     * Return whether the container supports connect i.e. field, exclGroup or subform
     * @return true if the container supports connect
     * @exclude
     */
	public boolean isConnectSupported() {
		return false;
	}
	
    /**
     * Get the connect node of this container (if the container supports connect) for a
     * given connection and usage. Connect is supported for field, exclGroup, and subform
     * @param sConnectionName the name of the connection.
     * @param eUsage the value of the usage property.
     * @param bCreate if TRUE create the connection if it doens't exist
     * @return the connect node of this subform for a given connection, a null XFANode if none is found.
     * If sConnectionName is empty or the container does not support connect a null XFANode is returned
     * @exclude
     */
	public Element getConnectNode(String sConnectionName, int eUsage, boolean bCreate) {
		// not all containers support connect
		if (!isConnectSupported())
			return null;

		if (StringUtils.isEmpty(sConnectionName))
			return null;
		
		for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
			if (child.isSameClass(XFA.CONNECTTAG)) {
				// peekAttribute will return an attribute only if it exists! (getAttribute will create a default attribute.)
				Attribute attr = ((Element)child).getAttribute(XFA.CONNECTIONTAG, true, false);
				if (attr == null) // shouldn't happen, just skip it
					continue;

				String connectionName = attr.toString();
				if (connectionName.equals(sConnectionName)) {
					int usage = ((Element)child).getEnum(XFA.USAGETAG);
					if (eUsage == usage) 
						return (Element)child;
				}
			}			
		}

		if (bCreate) {
			Element ret = getModel().createElement(this, getLastXMLChild(), "", XFA.CONNECT);
			ret.setAttribute(new StringAttr(XFA.CONNECTION, sConnectionName),XFA.CONNECTIONTAG);
			ret.setAttribute(eUsage,XFA.USAGETAG);
			return ret;
		}

		return null;
	}


	/**
	 * @see Element#isContainer()
	 *
	 * @exclude from published api.
	 */
	public boolean isContainer() {
		return true;
	}

	/**
	 * Does an Eventchild exist for the given activity? Note: includes the
	 * pseudo-events (calculate and validate elements).
	 * 
	 * @param sActivity
	 *            The name of the activity
	 * @param sRef SOM expression to the referenced object
	 */
	boolean isEventSpecified(String sActivity, String sRef) {
		EventTag event = getEvent(sActivity, sRef);
		return event != null && !StringUtils.isEmpty(event.getScriptText());
	}

	/**
	 * Returns whether or not this container's height is growable.
	 * If true, then the container is considered growable between range of
	 * [minH, maxH] or [0, infinity] when not specified.
	 * 
	 * @return true if this containers height can grow, false otherwise
	 */
	public boolean isHeightGrowable() {
		// 1) If h/w is specified (and not ""), then the container is not
		// growable and any min/max
		// value is irrelevant.
		// 2) Otherwise the container is considered growable. Any min/max
		// attribute that are specified will indicate the range of growableness.
		// The default range is [0, infinity].
		if (!isHeightGrowSupported())
			return false;

		// 1)
		Measurement oH = (Measurement) getAttribute(XFA.HTAG, true, false);
		if (oH != null && !oH.isEmpty())
			return false;

		// 2) if min and max are == then not growable
		Measurement oMaxH = (Measurement) getAttribute(XFA.MAXHTAG, true, false);

		if (oMaxH != null && !oMaxH.isEmpty()) {
			Measurement oMinH = (Measurement) getAttribute(XFA.MINHTAG, true, false);
			if (oMinH != null && !oMinH.isEmpty()) {
				UnitSpan oMaxHVal = oMaxH.getUnitSpan();
				UnitSpan oMinHVal = oMinH.getUnitSpan();

				if (oMaxHVal.lte(oMinHVal))
					return false;
			}
		}
		return true;
	}

	/**
	 * Returns whether or not this container support growable heights.
	 * This does
	 * not indicate whether the container height is currently growable, just
	 * whether it's supported by this container type.
	 *
	 * @exclude from published api.
	 */
	public boolean isHeightGrowSupported() {
		return false;
	}

	/**
	 * Returns whether or not this container's width is growable.
	 * If true then the container is considered growable between range of
	 * [minH, maxH] or [0, infinity] when not specified.
	 * 
	 * @return true if this containers height can grow, false otherwise
	 */
	public boolean isWidthGrowable() {
		// 1) If h/w is specified (and not ""), then the container is not
		// growable and any min/max
		// value is irrelevant.
		// 2) Otherwise the container is considered growable. Any min/max
		// attribute that are specified will indicate the range of growableness.
		// The default range is [0, infinity].
		if (!isWidthGrowSupported())
			return false;

		// 1)
		Measurement oW = (Measurement) getAttribute(XFA.WTAG, true, false);
		if (oW != null && !oW.isEmpty())
			return false;

		// 2) if min and max are == then not growable
		Measurement oMaxW = (Measurement) getAttribute(XFA.MAXWTAG, true, false);

		if (oMaxW != null && !oMaxW.isEmpty()) {
			Measurement oMinW = (Measurement) getAttribute(XFA.MINWTAG, true, false);
			if (oMinW != null && !oMinW.isEmpty()) {
				UnitSpan oMaxWVal = oMaxW.getUnitSpan();
				UnitSpan oMinWVal = oMinW.getUnitSpan();

				if (oMaxWVal.lte(oMinWVal))
					return false;
			}
		}
		return true;
	}

	/**
	 * Return whether or not this container support growable widths This does
	 * not indicate whether the container height is currently growable, just
	 * whether it's supported by this container type.
	 * 

* Comments Helps distinguish - * ie text draws supporting growing but arc draws do not. * * @exclude from published api. */ public boolean isWidthGrowSupported() { return false; } int measurementCompare(String sFirst, String sSecond) { // Return value -1 if first value < second // Return 0 if first value == second // Return 1 if first value > second UnitSpan firstm = new UnitSpan(sFirst); UnitSpan secondm = new UnitSpan(sSecond); if (firstm.equals(secondm)) return 0; else if (firstm.gt(secondm)) return 1; else return -1; } String measurementValidate(String sValue) { return UnitSpan.measurementValidate(sValue, false); } /** * Sets an attribute of this element. * This method treats the w/h attributes and their min/max counterparts * as mutually exclusive - setting w/h will remove any min/max and vice * versa. *

* In addition, there are clear rules to resolve conflicts: *

    *
  1. If h/w is specified, then the container is not growable and any * min/max value is irrelevant. *
  2. Otherwise the container is considered growable. Any min/max * attribute that are specified will indicate the range of growableness. * The default range is [0, infinity]. *
*

* In terms of setting min/max values, this method will guard against * setting min > max. * * @param attr * the attribute. * @param eTag * The XFA tag name of the attribute being set. * @see ProtoableNode#setAttribute(Attribute, int) * @see #isWidthGrowable() * @see #isHeightGrowable() */ public void setAttribute(Attribute attr, int eTag) { if (attr != null) { if ((eTag == XFA.HTAG || eTag == XFA.MINHTAG || eTag == XFA.MAXHTAG) && isHeightGrowSupported()) { if (eTag == XFA.HTAG) { // Remove any minH/maxH attributes removeAttr(null, XFA.MINH); removeAttr(null, XFA.MAXH); } else if (eTag == XFA.MINHTAG) { // Guard against min > max Attribute oMaxHAttr = getAttribute(XFA.MAXHTAG, true, false); if (oMaxHAttr != null) { Attribute oMinHAttr = attr; String sMinH = measurementValidate(oMinHAttr.toString()); String sMaxH = measurementValidate(oMaxHAttr.toString()); if (0 < measurementCompare(sMinH, sMaxH)) { // Oh-oh, trying to set min > max. Increase max. setAttribute(oMinHAttr, XFA.MAXHTAG); } } // Remove the h attribute removeAttr(null, XFA.H); } else if (eTag == XFA.MAXHTAG) { // Guard against max < min Attribute oMinHAttr = getAttribute(XFA.MINHTAG, true, false); if (oMinHAttr != null) { Attribute oMaxHAttr = attr; String sMaxH = measurementValidate(oMaxHAttr.toString()); String sMinH = measurementValidate(oMinHAttr.toString()); if (0 < measurementCompare(sMinH, sMaxH)) { // Oh-oh, trying to set max > min. Decrease min. setAttribute(oMaxHAttr, XFA.MINHTAG); } } // Remove the h attribute removeAttr(null, XFA.H); } } if ((eTag == XFA.WTAG || eTag == XFA.MINWTAG || eTag == XFA.MAXWTAG) && isWidthGrowSupported()) { if (eTag == XFA.WTAG) { // Remove any maxW/maxW attributes removeAttr(null, XFA.MINW); removeAttr(null, XFA.MAXW); } else if (eTag == XFA.MINWTAG) { // Guard against min > max Attribute oMaxWAttr = getAttribute(XFA.MAXWTAG, true, false); if (oMaxWAttr != null) { Attribute oMinWAttr = attr; String sMinW = measurementValidate(oMinWAttr.toString()); String sMaxW = measurementValidate(oMaxWAttr.toString()); if (0 < measurementCompare(sMinW, sMaxW)) { // Oh-oh, trying to set min > max. Increase max. setAttribute(oMinWAttr, XFA.MAXWTAG); } } // Remove the w attribute removeAttr(null, XFA.W); } else if (eTag == XFA.MAXWTAG) { // Guard against max < min Attribute oMinWAttr = getAttribute(XFA.MINWTAG, true, false); if (oMinWAttr != null) { Attribute oMaxWAttr = attr; String sMaxW = measurementValidate(oMaxWAttr.toString()); String sMinW = measurementValidate(oMinWAttr.toString()); if (0 < measurementCompare(sMinW, sMaxW)) { // Oh-oh, trying to set max > min. Decrease min. setAttribute(oMaxWAttr, XFA.MINWTAG); } } // Remove the w attribute removeAttr(null, XFA.W); } } } // Set the property super.setAttribute(attr, eTag); } /** * @exclude from public api. */ public void setFormInfo(FormInfo formInfo) { mFormInfo = formInfo; } // // Child Index Cache design overview // // The basic premise is to store indicies to the most-queried [0..1] properties // on a container. This helps particularly for containers with a large number // of children, and during layout when multiple requests are made on the same // property. // // The question is, when (and how often) do we refresh the cache? A cache refresh // iterates through all children, while a linear search iterates through half // the children on average (in practice probably more since many children aren't // found and we must search to the end to discover that). Cache regeneration also // entails a cascaded if/then/else which is somewhat more costly than the simple // lookup of a linear search. Worst case analysis therefore suggests that the // cache hit/refresh rate needs to be kept above 3 for uniform performance // improvement. // // Analysis of the XMLFormAgent test suite shows that 24% of queries are made // immediately after a change to the child list, 9% come one query later, and // 67% of queries come more than one query later. Qualitatively, this produces // 1,480K cache hits and 280K cache refreshes if we use the cache on each query. // However, if we ignore a dirty cache until we've had two queries in a row with // no intervening child list modifications, we generate 1,275 cache hits for // 210K refreshes -- an improvement from 5.3 to 6.1 hits/refresh. // // We also measured the results with our performance suite (dev_xtg/performance), // but the resolution (and repeatability) of these tests was insufficient to show // the results on smaller documents. Larger documents show a decisive win for // the cache (up to 18% overall for simple1000p.pdf). // private void refreshChildIndexCache() { // // Clear all the entries to prepare for properties which aren't found. // for (int i = 0; i < CHILD_INDEX_CACHE_SIZE; i++) mChildIndexCache[i] = null; // // Loop through the children, storing indicies to those we care about. // for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) { final int eChildTag = child.getClassTag(); // // In containers with lots of children, and are // the most common so we check for them first in order to avoid // the more expensive eTagToCacheSlot() call. // if (eChildTag == XFA.SUBFORMTAG || eChildTag == XFA.FIELDTAG) continue; // // If this eTag has a slot in the cache then it's one of the // more-often-queried properties (based on analysis of the // XMLFormAgent test suite) and we want to cache its index. // // Note: store only the first hit to mimic the behavior of a // linear search through the children. Watson 2285723 // int nSlot = eTagToCacheSlot(eChildTag); if ((nSlot >= 0) && (mChildIndexCache[nSlot] == null)) mChildIndexCache[nSlot] = child; } } private void setCacheDelayFlag(boolean bDelay) { // Always clear the dirty flag so we can see if any subsequent changes have been made setChildListModified(false); setDelayCacheRefreshFlag(bDelay); } private boolean getDelayCacheRefreshFlag() { return mbDelayCacheRefresh; } private final void setDelayCacheRefreshFlag(boolean bFlagValue) { mbDelayCacheRefresh = bFlagValue; } /** * @exclude from published api. */ public Node locateChildByClass(int eChildTag, int nIndex) { // // If this is a [0..1] property and we're caching some of our children's // indicies, then see if this is one of those children. // if (nIndex == 0 && !getModel().isLoading()) { int nSlot = eTagToCacheSlot(eChildTag); if (nSlot >= 0) { if (isChildListModified()) { // Children still getting moved around a lot. Don't bother generating cache yet.... setCacheDelayFlag(true); } else { if (getDelayCacheRefreshFlag()) { // Second cache request with no dirty in between. Go ahead and generate cache... refreshChildIndexCache(); setCacheDelayFlag(false); } else { // Cache still good: just use it. } Node child = mChildIndexCache[nSlot]; if (Assertions.isEnabled) { if (child == null) { assert(super.locateChildByClass(eChildTag, nIndex) == null); } else { assert(child.isSameClass(eChildTag)); assert(child == super.locateChildByClass(eChildTag, nIndex)); } } return child; } } } // Default to XFANodeImpl's implementation (a linear search) return super.locateChildByClass(eChildTag, nIndex); } private int eTagToCacheSlot(int eChildTag) { // // Note: tests are ordered by number of queries while processing the // forms in the XMLFormAgent test suite. // if (eChildTag == XFA.BORDERTAG) return 0; else if (eChildTag == XFA.BINDTAG) return 1; else if (eChildTag == XFA.MARGINTAG) return 2; else if (eChildTag == XFA.VALIDATETAG) return 3; else if (eChildTag == XFA.CALCULATETAG) return 4; else // a hint to help keep eTagToCacheSlot and cache size consistent if (Assertions.isEnabled) assert(5 == CHILD_INDEX_CACHE_SIZE); // // Note: tags in order after calculate are para, keep, break, occur // and assist. But a larger cache is slower to search and more // expensive to store. // return -1; } /** * Pure-virtual interface for clients of XFANode::compareVersions() which want changes * reported in terms of containers & properties instead of elements & attributes. * * The XFAContainerizer class (below) is used to convert the XFANode interfaces into the * XFAContainer interfaces. Your class should inherit from XFAContainer::ChangeLogger * and implement the 3 interfaces; you then create a XFAContainerizer to wrap your class * and pass that into XFANode::compareVersions(). * * @exclude from published api. */ public interface ChangeLogger { public void logPropChange(Node container, String sPropName, String sNewValue, Object userData); public void logValueChange(Node container, String sNewValue, Object userData); public void logChildChange(Node container, Node child, Object userData); } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy