com.adobe.xfa.template.containers.Container Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*
* 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:
*
* - If h/w is specified, then the container is not growable and any
* min/max value is irrelevant.
*
- 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);
}
}