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

com.adobe.xfa.ProtoableNode Maven / Gradle / Ivy

The 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;


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

import org.xml.sax.Attributes;

import com.adobe.xfa.content.ImageValue;
import com.adobe.xfa.ut.ExFull;
import com.adobe.xfa.ut.FindBugsSuppress;
import com.adobe.xfa.ut.MsgFormatPos;
import com.adobe.xfa.ut.Peer;
import com.adobe.xfa.ut.ResId;
import com.adobe.xfa.ut.StringUtils;


/**
 * A base class to represent protoable objects.
 * Any XFA object that is protoable can make use of prototypes.
 * Prototypes (or proto's) are used to reduce the redundancy of common
 * information repeated throughout a form.
 */
public class ProtoableNode extends Element {
	
	// Portions of this code implement AdobePatentID="B624"
	@SuppressWarnings("unused")
	private final static String patentRef = "AdobePatentID=\"B624\"";
	

	private static void establishProtoRelationship(Element parent,
			Node srcProtoChild, Node targetProtoChild, boolean bFull,
			boolean bCreate, boolean bMarkTransient, boolean bSrcIsExternal) {

		if (targetProtoChild != null
				&& targetProtoChild.isSameClass(srcProtoChild)) {
			if (targetProtoChild instanceof ProtoableNode) {
				// found an instance in our child list, so set up a proto
				// relationship.
				String sUse = "";
				if (targetProtoChild.isPropertySpecified(XFA.USEHREFTAG, false, 0))
					sUse = ((Element)targetProtoChild).getAttribute(XFA.USEHREFTAG).toString();
				else if (targetProtoChild.isPropertySpecified(XFA.USETAG,	false, 0))
					sUse = ((Element)targetProtoChild).getAttribute(XFA.USETAG).toString();
				//
				// Connect our child with the child of the proto if it doesn't
				// already have a proto relationship
				//
				if (StringUtils.isEmpty(sUse)) 
					((ProtoableNode) targetProtoChild).resolveProto((ProtoableNode)srcProtoChild, 
							bFull, bMarkTransient, bSrcIsExternal);
			}
		} 
		else if (bCreate) {

			Node ourChild = null;

			// mute the parent since we only want to issue the child added notification
			boolean bMute = false;
			if (parent != null) {
				bMute = parent.isMute();
				parent.mute();
			}

			try {
				if (srcProtoChild instanceof ProtoableNode)
					ourChild = ((ProtoableNode)srcProtoChild).createProto(parent, bFull);
				else {
					ourChild = srcProtoChild.clone(parent);
						
					// createProto will handle setting the transient and fragment flags 
					// if the source node is marked as such, but we need to do this if we clone
					if (srcProtoChild.isTransient() || parent.isTransient())
						bMarkTransient = true;
					
					if (srcProtoChild instanceof Element) {
						if (((Element)srcProtoChild).isFragment() || parent.isFragment())
							bSrcIsExternal = true;
					}
					else if (srcProtoChild instanceof TextNode) {
						if (((TextNode)srcProtoChild).isFragment() || parent.isFragment())
							bSrcIsExternal = true;
					}
				}

				// Watson 1509050: an attempt to set the usehref attribute was rejected by the form model,
				// because it can't create nodes during merge.  We don't have a great error message and
				// our resources are locked down, so use a generic one.  TODO: invent a better message.
				if (ourChild == null) {
					// Borrowing a suitably-generic message from the DOM code:
					// DOM_NO_MODIFICATION_ALLOWED_ERR,			"An attempt was made to modify an object where modifications are not allowed"
					MsgFormatPos message = new MsgFormatPos(ResId.DOM_NO_MODIFICATION_ALLOWED_ERR);
					throw new ExFull(message);
				}

				// Fix for Watson #1117306 - when the proto instance returned from locateChild isn't the
				// correct class (this can happen doing a match by name, rather than by class name) after
				// creating the correct proto instance, move it to where it should be located
				if (targetProtoChild != null) { // && !poTargetProtoChild->isSameClass (poSrcProtoChild))
					// mute so we don't issue the child added notification, this is done below
					ourChild.mute();
					parent.insertChild(ourChild, targetProtoChild, false);
					ourChild.unMute();
				}

				if (srcProtoChild.isDefault(true))
					ourChild.makeDefault();

				// Do the same behaviour for the fragment flag (which is
				// very similar to the transient flag).
				if (bMarkTransient)
					ourChild.isTransient(true, true);
	
				if (bSrcIsExternal) {
					if (ourChild instanceof Element)
						((Element)ourChild).isFragment(true, true);
					else if (ourChild instanceof TextNode)
						((TextNode)ourChild).isFragment(true);
				}
				if (parent != null) {
					// unmute the parent
					if (!bMute)
						parent.unMute();

					// let our peers know that we have added a child.
					parent.notifyPeers(Peer.CHILD_ADDED, ourChild.getClassAtom(), ourChild);
				}
			}
			catch (ExFull ex) {
				if (!bMute && parent != null)
					parent.unMute();
				throw ex;
			}
		}
	}
	/**
	 * 
	 * @return true if this child is already referenced by one of our children
	 */
	private static boolean hasID(Node srcProtoChild, List useIDs) {
		if (!(srcProtoChild instanceof Element))	//JavaPort: extra check. We might get #text nodes here.
			return false;
		
		if (useIDs != null && useIDs.size() > 0 && srcProtoChild.isPropertySpecified(XFA.IDTAG, false,0)) {
			String sID = ((Element)srcProtoChild).getAttribute(XFA.IDTAG).toString();
			if (!StringUtils.isEmpty(sID)) {
				 // loop through original children use id's
				for (int j = 0; j < useIDs.size(); j++) {
					String sUse = useIDs.get(j);

					// If this proto child is already connected by proto
					// reference by one of our children, we won't add it to
					// our list of children.
					if (sID.equals(sUse))
						return true;
				}
			}
		}
		return false;
	}
	
	private boolean mbCurrentlyResolvingProto; 
	private boolean mbExternalProtoFailed;
	private boolean mbExternalProtoResolved;
	private boolean mbHasExternalProto;
	
	/**
	 * The proto object, if any, after it has been resolved.
	 */
	private ProtoableNode mProto;
	
	/**
	 * The external prototype reference node used in the creation of this node.
	 */
	private ProtoableNode mExternalProto;

	private List mProtoed;
	
	/**
 	 * @exclude from published api.
	 */
	public ProtoableNode() {

	}

	/**
 	 * @exclude from published api.
	 */
	protected ProtoableNode(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);
	}
	
	/**
 	 * @exclude from published api.
	 */
	protected ProtoableNode(Element parent, Node prevSibling) {
		super(parent, prevSibling);
	}
	
	/**
	 * Add a node to the protoed list
	 * @param protoable
	 * @exclude from published api.
	 */
	private void addProtoed(ProtoableNode protoable) {
		if (mProtoed == null)
			mProtoed = new ArrayList();
		
		mProtoed.add(protoable);
	}

	/**
	 * @exclude from published api.
	 */
	private void clearExternalProtos() {
		// clear the external reference
		mExternalProto = null;
		
		//
		// Remove any children we inherited from an external proto
		//
		Node child = getLastXMLChild(); 
		while (child != null) {
			Node prevChild = child.getPreviousXMLSibling();
			if (child instanceof ProtoableNode && ((ProtoableNode)child).isFragment()) {
				child.remove();
			}
			
			child = prevChild;
		}
		
		//
		// Clear any attributes we inherited from our external proto
		//
		SchemaPairs attrs = getNodeSchema().getValidAttributes();
		if (attrs != null) {
			for (int i = 0; i < attrs.size(); i++) {
				Attribute peerAttr = getAttribute(attrs.key(i), true, false);
				if (peerAttr != null && getAttrProp(getAttrIndex(peerAttr), AttrIsFragment)) {
					if (attrs.key(i) == XFA.IDTAG)
						forceID("");
					else
						setAttribute(null, attrs.key(i));
				}
			}
		}
	}

	private void clearProtos() {
		if (mProto != null) {
			mProto.removeProtoed(this);
			mProto = null;
		}
		//
		// Remove any children we inherited from a byVal or byRef proto
		//
		Node child = getLastXMLChild();
		while (child != null) {
			Node prevChild = child.getPreviousXMLSibling();
			if (child instanceof ProtoableNode) {
				ProtoableNode protoChild = (ProtoableNode)child;
				if (protoChild.hasProto()) {
					if (protoChild.isDefault(false))
						child.remove();
					else
						protoChild.performResolveProtos(false);
				}
			}
			child = prevChild;
		}
		//
		// Clear any attributes we inherited from our byVal proto
		//
		SchemaPairs attrs = getNodeSchema().getValidAttributes();
		if (attrs != null) {
			for (int i = 0; i < attrs.size(); i++) {
				String aAttr = XFA.getAtom(attrs.key(i));
				int index = findAttr(null, aAttr);
				if (index != -1 && getAttrProp(index, AttrIsDefault)) {
					removeAttr(null, aAttr);
				}
			}
		}
	}

	/**
	 * clone will establish any proto relationships
 	 * @exclude from published api.
	 */
	public Element clone(Element parent, boolean bDeep) {
		ProtoableNode clone = (ProtoableNode) super.clone(parent, bDeep);
		ProtoableNode proto = getProto();
		if (proto != null) {
			// remove any existing proto relationship
			if (clone.mProto != null)
				clone.mProto.removeProtoed(this);
			
			// set the proto relationship
			clone.mProto = proto;
			proto.addProtoed(clone);
		}
		clone.mbHasExternalProto = mbHasExternalProto;
		return clone;
	}

	/**
 	 * @exclude from published api.
	 */
	@FindBugsSuppress(code="RCN")	// parent
	public ProtoableNode createProto(Element parent, boolean bFull /* = false */) {
		// ensure the parent isn't null
		assert (parent != null);
		boolean bMute = false;
		ProtoableNode ret = null;
		try {
			// watson bug 1511417, ensure createProto by default doesn't notify.
			// if it does it will notify layout and cause it to be damaged.
			// we only do this if bFull = false because full copies nodes without 
			// keeping the proto linkage
			if (parent != null && !bFull) {
				bMute = parent.isMute();
				parent.mute();
			}
		
			boolean bIsSrcExternal = false;
			String aName = getPrivateName();	// Watson 1760320.  Use getPrivateName and setPrivateName when resolving protos.
			Model thisModel = getModel();
			Model parentModel = parent != null ? parent.getModel() : null;
			
			if ( parent != null) {
				// if we have a different model then we have an external resource
				if (parentModel != thisModel)
					bIsSrcExternal = true;
			}
			
			ProtoableNode retNode = null;
			
			// create the node
			if (parentModel != null)
				retNode = (ProtoableNode)parentModel.createNode(getClassTag(), parent, "", getNS(), true);			
			else
				retNode = (ProtoableNode)thisModel.createNode(getClassTag(), parent, "", getNS(), true);
			
			// JavaPort: How the namespace is getting set is quite different from C++ and needs to be synchronized.
			//           In the meantime, this hack is in place to get specific cases working.
			retNode.setDOMProperties(getModel().getHeadNS(), retNode.getLocalName(), retNode.getXMLName(), null);			

			ret = retNode;
			if (!StringUtils.isEmpty(aName))
				ret.setPrivateName(aName);	// Watson 1760320.  Use getPrivateName and setPrivateName when resolving protos.
				
//				jfDomNode oParentDomPeer(pParent->getDomPeer());
//				jfDomNode oSrcDomPeer(getDomPeer());
//				jfDomNode oNewDomPeer(pRetImpl->getDomPeer());

			// Do the same behaviour for the fragment flag (which is
			// very similar to the transient flag).
//				assert( ! oSrcDomPeer.isNull() && ! oNewDomPeer.isNull() && !oParentDomPeer.isNull());
			if (isTransient() || parent.isTransient() )
				ret.isTransient(true, true);

			// set the fragment flag
			if (isFragment() || bIsSrcExternal)
				ret.isFragment(true, true);

			ret.resolveProto(this, bFull, false, bIsSrcExternal);

			if (!bFull)
				ret.makeDefault();
			else
				ret.makeNonDefault(false);
			
			// isPropertySpecified(XFA::USEHREF) does not work for a nested fragref since the
			// USEHREF is not copied - this was specifically why hasExternalProto() was originally added.
			// so only set this flag if this node also has a external proto
			ret.mbHasExternalProto = mbHasExternalProto;

			if (!bMute && parent != null)
				parent.unMute();
		}
		catch (ExFull ex) {
			if (!bMute && parent != null)
				parent.unMute();

			// rethrow
			throw ex;
		}

		return ret;
	}

	/**
	 * We need this method so that derived classes can explicitly call the
	 * getAttribute() method on Element.
	 * @see Element#getAttribute(int, boolean, boolean)
	 *
 	 * @exclude from published api.
	 */
	public Attribute elementGetAttribute(int eTag, boolean bPeek/* =false */,
			boolean bValidate/* =false */) {
		return super.getAttribute(eTag, bPeek, bValidate);
	}
	
	/**
	 * @exclude from published api.
	 */
	public final boolean externalProtoFailed() {
		if (mbExternalProtoFailed)
			return true;
		
		if (mExternalProto != null)
			return mExternalProto.externalProtoFailed();
		
		return false;
	}
	
	/**
	 * @exclude from published api.
	 */
	public void fetchIDValues(List idValues) {
		// Do nothing
	}
	
	/**
	 * @exclude from published api.
	 */
	public ProtoableNode getProtoed(int nIndex) {
		if (mProtoed == null)
			return null;
		
		if (nIndex >= mProtoed.size())
			return null;
		
		return mProtoed.get(nIndex);
	}

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

	/**
	 * @exclude from published api.
	 */
	public void updateIDValues(String sPrefix, List oldReferenceList) {
		// Subclasses which contain ID references in attributes must override this method....
	}

	/**
	 * Go through and copy all properties from the proto
	 * 
	 * @exclude from published api.
	 */
	protected final void fullyResolve(boolean bClearProtoLink /* = true */) {
		// clear transient flag;
		super.makeNonDefault(true);

		// copy all properties over;
		SchemaPairs attrs = getNodeSchema().getValidAttributes();
		if (attrs != null) {
			for (int i = 0; i < attrs.size(); i++) {
				// don't copy id, use, or useHref
				int eTag = attrs.key(i);
				if (eTag == XFA.IDTAG || eTag == XFA.USETAG || eTag == XFA.USEHREFTAG) {
					continue;
				}

				// if we have the attribute set, copy it to "this"
				Attribute attr = getAttribute(eTag, true, false);
				if (attr != null)
					setAttribute(attr, eTag);
			}
		}

		// collection of all the children, that need to be proto'd
		NodeList protoChildren = new ArrayNodeList();
		NodeList protoProperties = new ArrayNodeList();

		resolveAndEnumerateChildren(protoProperties, protoChildren, false, false);

		int nProtoProp = protoProperties.length();
		for (int i = 0; i < nProtoProp; i++) {
			Node child = (Node)protoProperties.item(i);

			// ensures the property is a child of "this"
			int nLookupIndex = child.getIndex(false);
			
			// JavaPort: need explicit handling for resolving TextNode since it is not an Element
			if (child instanceof TextNode) {
				child = getText(false, false, false);
				
				child.setDefaultFlag(false, true);
			}
			else
				child = getElement(child.getClassTag(), false, nLookupIndex, false, false);

			if (child instanceof ProtoableNode)
				((ProtoableNode) child).fullyResolve(bClearProtoLink);
		}

		Node protoOneOfChild = getOneOfChild(true, false);
		if (protoOneOfChild != null) {
			Node child = protoOneOfChild;

			// ensures the oneOfChild is a child of "this"
			child = getOneOfChild();

			if (child instanceof ProtoableNode)
				((ProtoableNode) child).fullyResolve(bClearProtoLink);
		}

		int nProtoChildren = protoChildren.length();
		for (int i = 0; i < nProtoChildren; i++) {
			Node child = (Node) protoChildren.item(i);

			if (child instanceof ProtoableNode)
				((ProtoableNode) child).fullyResolve(bClearProtoLink);
		}
		
		if (bClearProtoLink) {

			if (mProto != null)
				mProto.removeProtoed(this);
			
			// clear the flags
			mProto = null;
			mExternalProto = null;
			
			mbHasExternalProto = false;
			mbExternalProtoFailed = false;
			mbExternalProtoResolved = false;
		}
	}
	
	/**
	 * @exclude from published api.
	 */
	public Attribute getAttribute(int eTag, boolean bPeek, boolean bValidate) {
		// first look for attr, and validate
		Attribute attr = super.getAttribute(eTag, true, bValidate);

		if (attr != null)
			return attr;

		if (hasProto()) {
			// Is the default attribute value context sensitive? i.e. is it's
			// value a function of other surrounding nodes/values?
			// If so and the proto'd node does not have the attribute specified
			// then we call defaultAttribute() on 'this' rather
			// than the proto. This is to address watson 1430554. It has been
			// determined that we should always be doing this, that
			// all default attributes should be calculated relative to their
			// actual context, however doing so broke
			// some of the xtg test suite and it's too late in the release to
			// decipher the risk of such a sweeping change.
			// The new behaviour will be limited to margin insets only to limit
			// risk.
			if (isContextSensitiveAttribute(eTag)) {
				if (mProto.isSpecified(eTag, Element.ATTRIBUTE, true, 0))
					return mProto.getAttribute(eTag, bPeek, false);
			} else
				return mProto.getAttribute(eTag, bPeek, false);
		}
		// null value
		if (bPeek)
			return null;

		return defaultAttribute(eTag);
	}
	
	/**
	 * get the named attribute.
	 * 
	 * @param aAttrName - the attribute name.
	 * @param bSearchProto - whether to search protos.
	 * @return Attribute object, which may be null.
	 * @exclude
	 */
	@FindBugsSuppress(code="ES")
	public Attribute getAttributeByName(String aAttrName, boolean bSearchProto /* = false */ ) {
		Attribute attr = super.getAttributeByName(aAttrName, bSearchProto);
		
		if (attr == null && bSearchProto && hasProto()) {
			if (aAttrName != XFA.ID && aAttrName != XFA.USE && aAttrName != XFA.USEHREF)
				attr = getProto().getAttributeByName(aAttrName, true);
		}
		
		return attr;
	}

	/**
 	 * @exclude from published api.
	 */
	public Element getElement(int eTag, boolean bPeek/* =false */,
			int nOccurrence/* =0 */, boolean bReturnDefault /* =false */,
			boolean bValidate /* =false */) {
		// only validate here
		Element child = super.getElement(eTag, true, nOccurrence, false, bValidate);

		if (child != null)
			return child;

		// Find in the proto
		if (hasProto()) {
			// check if the proto has an instance of the element
			child = mProto.getElement(eTag, true, nOccurrence, false, false);

			// if we are only peeking return the element from the proto
			if (bPeek) {
				if (child != null)
					return child;
				if (!bReturnDefault) // if we are not returning a default then quit now
					return null;
			}
			// ensure that all previous occurrences are copied over
			if (nOccurrence > 0) {
				for (int i = 0; i < nOccurrence; i++) {
					// see if we have a copy of the element
					Node otherChild = super.getElement(eTag, true, i, false, false);
					if (otherChild != null)
						continue;
					
					// see if the proto has a instance of the element
					otherChild = mProto.getElement(eTag, true, i, false, false);
					if (otherChild != null) {
						// we have an instance on the proto, so lets copy it over
						
						// Mark the dom document as loading to ensure that the nodes
						// are not marked as dirty.
						final boolean previousWillDirty = getWillDirty();
						setWillDirty(false);
						try {
							ProtoableNode node = (ProtoableNode) otherChild;
							node.createProto(this, false);
						}
						finally {
							setWillDirty(previousWillDirty);
						}
					} else // the proto doesn't have a instance so we will create a default
						break;
				}
			}
			// now process the current occurrence
			// With the SVG integration we'll encounter XFANodes that are *not*
			// protoable.  We're not worried about making copies of them in the
			// form dom, since they are expected to be read-only: not accessible
			// via script.
			if (child instanceof ProtoableNode) {
				// we have an instance on the proto, so lets copy it over
				ProtoableNode node = (ProtoableNode)child;
				
				// Mark the dom document as loading to ensure that the nodes
				// are not marked as dirty. 
				final boolean previousWillDirty = getWillDirty();
				setWillDirty(false);
				
				try {				
					return node.createProto(this, false);
				}
				finally {
					setWillDirty(previousWillDirty);
				}
			}
		}

		// create it
		if (!bPeek || bReturnDefault)
			return super.getElement(eTag, bPeek, nOccurrence, bReturnDefault, false);

		return null;
	}

	/**
 	 * @exclude from published api.
	 */
	public Node getOneOfChild(boolean bPeek /* =false */, boolean bReturnDefault/* =false */) {
		Node node = super.getOneOfChild(true, false);
		if (node != null)
			return node;

		// Find out if there's a child of our proto
		if (hasProto()) {
			node = mProto.getOneOfChild(true, false);
			if (node != null) {
				// create as child of this node
				if (!bPeek) {
					
					// Mark the dom document as loading to ensure that the nodes
					// are not marked as dirty. 
					final boolean previousWillDirty = getWillDirty();
					setWillDirty(false);
					
					try {
						// JavaPort: In the Java version, we need to special-case the
						// classes that are derived from ProtoableNode in C++, but not in Java.
						if (node instanceof ProtoableNode)
							return ((ProtoableNode) node).createProto(this, false);
						else
							// Just clone - no proto references to resolve
							return node.clone(this);
					}
					finally {
						setWillDirty(previousWillDirty);
					}
				}
				else
					return node;
			}
		}

		// create a default
		if (!bPeek || bReturnDefault)
			return super.getOneOfChild(bPeek, bReturnDefault);

		return null;
	}

	/**
 	 * @exclude from published api.
	 */
	void getPI(List pis, boolean bCheckProtos) {
		super.getPI(pis, bCheckProtos);

		if (pis.size() == 0 && bCheckProtos && hasProto())
			mProto.getPI(pis, bCheckProtos);
	}
	/**
 	 * @exclude from published api.
	 */
	public void getPI(String aPiName, List pis, boolean bCheckProtos) {
		assert (aPiName != null);

		super.getPI(aPiName, pis, bCheckProtos);

		if (pis.size() == 0 && bCheckProtos && hasProto())
			mProto.getPI(aPiName, pis, bCheckProtos);
	}
	
	/**
 	 * @exclude from published api.
	 */
	public void getPI(String aPiName, String sPropName, List pis,
			boolean bCheckProtos) {
		assert (aPiName != null);

		super.getPI(aPiName, sPropName, pis, bCheckProtos);

		if (pis.size() == 0 && bCheckProtos && hasProto())
			mProto.getPI(aPiName, sPropName, pis, bCheckProtos);
	}

	/**
	 * Get the reference node for this node
	 * 
	 * @return a reference node, a null node if there is none.
	 *
 	 * @exclude from published api.
	 */
	public ProtoableNode getProto() {
		return mProto;
	}

	/**
	 * Get the external prototype reference node used in the creation of this node
	 * 
	 * @return a external reference node, a null node if there is none.
	 * 
	 * @exclude from published api.
	*/
	public ProtoableNode getExternalProtoSource()
	{
		return mExternalProto;
	}
	
	/**
	 * Gets the first text node child. New for the Java Port.
	 * Modeled on #getElement.
	 * @param bPeek
	 * @param bReturnDefault
	 * @param bValidate
	 * @return A TextNode if found (or created). null otherwise.
	 *
	 * @exclude from published api.
	 */
	final public TextNode getText(boolean bPeek /* = false */,
								  boolean bReturnDefault /* = false */,
								  boolean bValidate /* = false */) {
		// only validate here
		TextNode child = super.getText(true, bReturnDefault, bValidate);
		if (child != null)
			return child;
		
		// Find in the proto.
		if (hasProto()) {

			// Check if the proto has an instance of the TextNode
			child = mProto.getText(true, false, false);
			
			// If we are only peeking return the TextNode from the proto.
			if ((child != null) && bPeek)
				return child;

			// See if we have a copy of the TextNode.
			TextNode otherChild = super.getText(true, false, false);
			
			// See if the proto has a instance of the TextNode.
			if (otherChild == null)
				otherChild = mProto.getText(true, false, false);
			
			if (otherChild != null)
				return otherChild.createProto(this, otherChild.getText(), false);
		}

		// Still not found? Create a default TextNode.
		if (!bPeek || bReturnDefault) {
			return super.getText(bPeek, bReturnDefault, false);
		}
		return null;
	}

	/**
	 * @exclude from published api.
	 */
	public boolean hasExternalProto() {
		return mbHasExternalProto;
	}
	
	/**
	 * Check if this node has a reference node
	 * 
	 * @return true if this node has a reference node, else false
	 *
 	 * @exclude from published api.
	 */
	public boolean hasProto() {
		return mProto != null;
	}


	/**
	 * @exclude from published api.
	 */
	public boolean isContextSensitiveAttribute(int eTag) {
		// If it's the locale tag, and it's not specified on the current node,
		// then check to see if it's proto has the attribute specified.  If it
		// does, then go ahead and use it.
		return (eTag == XFA.LOCALETAG);
	}
	
	/**
	 * @see Node#isDefault(boolean)
	 * @exclude from published api.
	 */
	public boolean isDefault(boolean bCheckProto/*true*/) {
		boolean bDefault = super.isDefault(false);

	    //Watson 1430554 - must check proto chain where any of the protos have an explicit empty (non-default) element specified.
		if (bCheckProto && bDefault && hasProto())
			bDefault = getProto().isDefault(bCheckProto);

		return bDefault;
	}

	/**
 	 * @exclude from published api.
	 */
	public boolean isSpecified(int eTag, int eType, boolean bCheckProtos,
			int nOccurrence) {

		if (super.isSpecified(eTag, eType, bCheckProtos, nOccurrence))
			return true;

		if (bCheckProtos && hasProto()) {
			// We don't inherit id, use, usehref.
			if (eTag != XFA.IDTAG && eTag != XFA.USETAG
					&& eTag != XFA.USEHREFTAG) {
				return mProto.isSpecified(eTag, eType, true, nOccurrence);
			}
		}

		return false;
	}
	
	/**
     * This method will be called whenever its state changes.
	 * This will also modify the eventType and notify the parent
	 * of this tree
	 * @exclude from published api.
     */
	@Override
    public void	notifyPeers(int eventType, 
							String arg1, 
							Object arg2) {
		
		// XFA doesn't notify of changes during loads, there is no need and it improves load performance
		if (getModel() == null || getModel().isLoading())
			return;

		// send notification for this node
		super.notifyPeers(eventType, arg1, arg2);

		// now notify Proto nodes;
		if (mProtoed != null && getModel().allowUpdates()) {
			
			final int nProtoed = mProtoed.size();
			if (nProtoed > 0) {
				
				Element parent = getXFAParent();
				int newEventType;
				Object newArg2 = arg2;
				// 1. covert the message
				switch (eventType) {
				
					case ATTR_CHANGED:
						newEventType = PROTO_ATTR_CHANGED;
						newArg2 = this; // context node will be this
						break;
					case CHILD_ADDED:
						newEventType = PROTO_CHILD_ADDED;
						break;
					case CHILD_REMOVED:
						newEventType = PROTO_CHILD_REMOVED;
						break;
					case VALUE_CHANGED:
						newEventType = PROTO_VALUE_CHANGED;
						newArg2 = this; // context node will be this
						break;

					case DESCENDENT_ATTR_CHANGED:
						newEventType = PROTO_DESCENDENT_ATTR_CHANGED;
						break;
					case DESCENDENT_VALUE_CHANGED:
						newEventType = PROTO_DESCENDENT_VALUE_CHANGED;
						break;
					case DESCENDENT_ADDED:
						newEventType = PROTO_DESCENDENT_ADDED;
						break;
					case DESCENDENT_REMOVED:
						newEventType = PROTO_DESCENDENT_REMOVED;
						break;
					
					default:
						return; // we don't know the event so we are done
				}

				
				// 2. Send the message to all the protoed nodes
				for (int i = 0; i < nProtoed; i++) {
					
					ProtoableNode proto = mProtoed.get(i);

					// mute the proto parent if its source node is the same parent as this. because the 
					// recursive call will handle notificaions
					Element mutedNode = null;
					try {
						if (proto.notifyParent()) {
							
							Element protoParent = proto.getXFAParent();
						
							if (!protoParent.isMute() && 
								protoParent instanceof ProtoableNode &&
								((ProtoableNode)protoParent).getProto() == parent) {
								
								mutedNode = protoParent;
								mutedNode.mute();
							}							
						}
						// notify the proto nodes
						proto.notifyPeers(newEventType, arg1, newArg2);
					}
					finally {
						if (mutedNode != null)
							mutedNode.unMute();
					}
				}
			}
		}
		// mute the proto parent if its source node is the same parent as this.
		sendParentUpdate(eventType, arg1, arg2);
    }

	/**
 	 * @exclude from published api.
	 */
	public boolean performResolveProtos(boolean bResolveExternalProtos) {
		ProtoableNode proto = null;
		int eClassTag = getClassTag();
	    //	
		// If bCurrentlyResolvingProto is already on,
		// then we've detected a circular reference in protos!
	    //	
		if (mbCurrentlyResolvingProto) {
			MsgFormatPos message = new MsgFormatPos(ResId.CircularProtoException);
			message.format(getSOMExpression());
			throw new ExFull(message);
		}
		//
		// Set default internal vars.
		//
		mbHasExternalProto = false;
		mbExternalProtoFailed = false;
		mbExternalProtoResolved = false;
		mbCurrentlyResolvingProto = true;
		
		IDValueMap idValueMap = new IDValueMap();
		
    	try {	
    		boolean bMarkAsTransient = false;
    		
    		int eAttrTag = -1;
    		StringBuilder sAttrValue = new StringBuilder();    		
    		
    		// If an element contains both a use and a usehref attribute, the usehref attribute
    		// will be honoured (at the expense of the use attribute) by XFA 2.3 processors.
    		// This allows a separate prototype to be used when rendered on legacy XFA systems.
    		// Check usehref first, and fall back to use if it's not found.
    		if (bResolveExternalProtos && isPropertySpecified(XFA.USEHREFTAG, false, 0)) {
    			eAttrTag = XFA.USEHREFTAG;
    			sAttrValue.append(getAttribute(eAttrTag,false, false).toString());
    			
    			// stop here if the value is empty
    			if (sAttrValue.length() == 0)
    				return false;
    			
    			// See if it's an external (fragment) reference.  External references
    			// are of this form:
    			//		usehref="URL#XML_ID" 
    			//		usehref="URL#som(SOM_expr)"
    			//
    			// The URL part may be "." to refer to the current document, i.e.:
    			// Internal references will be something like:
    			//		usehref=".#XML_ID"
    			//		usehref=".#som(SOM_expr)"
    			boolean bIsInternalProto = ((sAttrValue.length() > 1) && (sAttrValue.charAt(0) == '.') && (sAttrValue.charAt(1) == '#'));
    			if (bIsInternalProto) {
    				// Massage the ".#XML_ID" or ".#som(SOM_expr)" value into
    				// the form that's acceptable for the use attribute, which
    				// findInternalProto expects.
    				sAttrValue.delete(0, 1);	// Chop off ".", leaving either "#XML_ID" or #som(SOM_expr)
    				if (sAttrValue.length() > 5 &&
    						sAttrValue.charAt(1) == 's' &&
    						sAttrValue.charAt(2) == 'o' &&
    						sAttrValue.charAt(3) == 'm' &&
    						sAttrValue.charAt(4) == '(' &&
    						sAttrValue.charAt(sAttrValue.length() - 1) == ')') {
    					sAttrValue.delete(0, 5);	// Trim "#som("
    					sAttrValue.delete(sAttrValue.length() - 1, sAttrValue.length());	// Trim trailing ')'.
    				}
    			}
    			
    			if (bIsInternalProto) {
    				// Apparently an internal proto referenced in a "usehref" attribute.
    				proto = findInternalProto(eClassTag, eClassTag, this, sAttrValue.toString(), false);
    			}
    			else {
    				mbHasExternalProto = true;
    				
    				proto = findExternalProto(eClassTag, eClassTag, sAttrValue.toString(), false);
    				
    				mbExternalProtoFailed = (proto == null);
    				
    				bMarkAsTransient = getAppModel().getExternalProtosAreTransient();
    				
    				// Install the ID Value map on the model so that all references to IDs
    				// (whether definition or usage) can be remapped to new non-conflicting
    				// values.
    				idValueMap.installOnModel(getModel());
    				
    				// If we're doing an explicit hard resolve (e.g. not transient and a 
    				// href handler is specified) such as when saving for PDF, remove the 
    				// attribute in order to ensure Acrobat doesn't attempt to re-resolve it.
    				// Not needed as it's no longer a external proto ref after being 
    				// fully resolved.
    				// Note also that we need to remove the attr regardless of whether the 
    				// resolve was successful - also to avoid Acrobat trying to resolve it.
    				if (!bMarkAsTransient && getAppModel().getHrefHandler() != null) {
    					removeAttr(null, getAtom(eAttrTag));
    				}
    				
    			}
    		}
    		else if (proto == null) {
    			
    			eAttrTag = XFA.USETAG;
    			sAttrValue.setLength(0);
    			
    			//C++ CL: 680258
    			Attribute attr = getAttribute(eAttrTag, true, false);
    			sAttrValue.append(null==attr?"":attr.toString());
    			
    			if (bResolveExternalProtos) {
    				// will be processed on second pass when we look at use attributes
    				// so return now.
    				if (sAttrValue.length() > 0)
    					return false;  
    			}
    			else {
    				// stop here if the value is empty
    				if (sAttrValue.length() == 0)
    					return false;	
    				
    				boolean bIsFragmentDoc = getAppModel().getIsFragmentDoc();
    				
    				// should not have a fragment file resolving use attributes
    				assert (!bIsFragmentDoc); 
    				if (!bIsFragmentDoc)
    					proto = findInternalProto(eClassTag, eClassTag, this, sAttrValue.toString(), false);
    				else {
    					getModel().removeIDReferenceNode(this);
    					return true; // return TRUE when the node is removed
    				}
    			}
    		}
    		
    		// remove this node from the list of nodes that need to be resolved
    		// must be done before we call resolveProto because we may add it again.
    		// or if there is an error to ensure we don't process the same node more than once
    		getModel().removeIDReferenceNode(this);
    		
    		if (proto == null || proto == this) {
    			//
    			// Upon failure return true because there's
    			// no point trying to resolve this proto again.
    			//
    			foundBadAttribute(eAttrTag, sAttrValue.toString());
    			return true;
    		}
    		
    		resolveProto(proto, mbHasExternalProto, bMarkAsTransient, mbHasExternalProto);
    		
    		// Watson 1502962.  This fixes the problem by disabling the recent code that caused the problem.  To be revisited.
    		if (idValueMap.isActive()) {
    			if (!idValueMap.verifySelfContained(sAttrValue.toString(), getSOMExpression(), proto)) {
    				// the reference is not self contained so clear the result
    				// a message was already logged by verifySelfContained
    				clearExternalProtos();
    			}
    		}
    		return true; // return true when the node is removed
    	} 
    	finally {
    		idValueMap.destroy();
            // Make sure bCurrentlyResolvingProto gets reset on exit.
    		mbCurrentlyResolvingProto = false;
    	}
	}
	
	/**
	 * @exclude from published api.
	 */
	public void preSave(boolean bSaveXMLScript /* = false */) {
		
		if (bSaveXMLScript) {
			
			if (getSaveXMLSaveTransient() && hasProto()) {
				
				// watson bug 1862548 ensure the contents of exdata
				// are saved out when saveXML is called.
				boolean bOldMuteValue = isMute();
				mute();
				
				try {
					final boolean previousWillDirty = getWillDirty();
					setWillDirty(false);
					try {					
						boolean bWasDefault = isDefault(false);
						
						// copy all properties, but also keep the proto links
						fullyResolve(false);
						
						// restore the default flag.
						if (bWasDefault)
							makeDefault();
					}
					finally {
						setWillDirty(previousWillDirty);
					}
				}
				finally {
					if (!bOldMuteValue)
						unMute();
				}
			}
		}
		
		super.preSave(bSaveXMLScript);
	}

	/**
	 * Remove a node from the protoed list
	 * @param protoable - the node to remove
	 */
	private void removeProtoed(ProtoableNode protoable) {
		if (mProtoed == null)
			return;
		
		// Watson 1602703 (performance).  We were spending a lot of time shutting
		// down; in particular running through very long lists of protos.  When
		// the model is NULL, that means we are in the process of shutting down,
		// so don't bother with this needless maintenance.
		if (getModel() == null) {
			// Just to be on the safe side, delete our list of protos and NULL it out.
			// We're not really removing the specified proto so we could end up with
			// pointers to invalid nodes otherwise.  This would only be an issue if
			// this node were kept alive longer than its model.
			if (mProtoed != null) {
				mProtoed.clear();
				mProtoed = null;
			}
			
			return;
		}
		
		int nSize = mProtoed.size();
		for (int nIndex = 0; nIndex < nSize; nIndex++) {
			if (mProtoed.get(nIndex) == protoable) {
				mProtoed.remove(nIndex);
				break;
			}
		}
	}

	/**
	 * remove any existing proto children and re-resolve the proto
	 * @param bFull see resolveProto
	 */
	
	public final boolean reResolveProto(boolean bFull) {
		ProtoableNode proto = getProto();
		if (proto != null) {
			clearProtos();
			return resolveProto(proto, bFull,false,false);
		}

		return false;
	}

	/**
	 * @exclude form public api.
	 */
	public boolean resolveProto(ProtoableNode srcProto, boolean bFull, boolean bMarkTransient /* = false */, boolean bSrcIsExternal /* = false */) {
		
		if (srcProto == this) {
			assert false; // Circular proto Ref
			return false;
		}
		
		assert (isSameClass(srcProto));

		// check if we doing a re-resolve remove old connecton
		if (hasProto() ) {
			// check if the same proto -> already done
			if (srcProto == mProto)
				return true;

			// clear any old protos
			clearProtos();
		}
		// if we process an external proto again, make sure we reset this node
		if (bSrcIsExternal) {
			clearExternalProtos();
			mExternalProto = srcProto;
		}
		
		// external fragments can reference the host doc so don't clear external fragments

		// if we are an external proto, make it as resolved
		if (mbHasExternalProto && bSrcIsExternal)
			mbExternalProtoResolved = true;

		//
		// We can't go on until our prototype gets fully resolved.
		//
		AppModel sourceAppModel = srcProto.getAppModel();
		assert (sourceAppModel != null);
		boolean bSourceResolved = false;
		// first check if the use href has been processed
		if (srcProto.isPropertySpecified(XFA.USEHREFTAG, false, 0) && 
			!srcProto.mbExternalProtoResolved) {
			srcProto.performResolveProtos(true);
		}
		// usehref wasn't there or we didn't resolve it, now try the use attribute
		else if (!bSourceResolved && 
			srcProto.isPropertySpecified(XFA.USETAG, false, 0) &&
			 !srcProto.hasProto() &&
			!sourceAppModel.getIsFragmentDoc()) {
			srcProto.performResolveProtos(false);
		}

		if (!bFull) {
			// remove any existing proto relationship
			if (mProto != null)
				mProto.removeProtoed(this);
			
			// set the proto relationships
			mProto = srcProto;
			srcProto.addProtoed(this);


			//Ensure that the content type for an exData element gets copied
			//or else there may be problems recognizing rich text
			if (srcProto.isSameClass(XFA.EXDATATAG) &&
				srcProto.isPropertySpecified(XFA.CONTENTTYPETAG, false,0)) {
				// don't send any notifications this can cause the layout to be damaged
				// and thus cause a relayout.
				boolean bMute = isMute();
				mute();
				setAttribute(srcProto.getAttribute(XFA.CONTENTTYPETAG), XFA.CONTENTTYPETAG);
				if (!bMute)
					unMute();
			}

			// no children being proto'd
			if (srcProto.isLeaf())
				return true;
		}
		else {
			mProto = null;
		}

		// unlock so this node can be modified by the resolve
		boolean bWasLocked = getLocked();
		unLock();

		//
		// Copy any attributes which we don't already have
		//
		if (bFull) {
			SchemaPairs attrs = getNodeSchema().getValidAttributes();
			
			IDValueMap idValueMap = getModel().getIDValueMap();
			
			if (attrs != null) {
				for (int i = 0; i < attrs.size(); i++) {
					// don't copy usehref for external proto resolution
					int eTag = attrs.key(i);
					if (eTag == XFA.USEHREFTAG) {
						continue;
					}

					// If the proto defines the attribute and we don't... copy it.
					if (srcProto.isPropertySpecified(eTag, true, 0) &&
						!isPropertySpecified(eTag, true, 0)) {
						if (eTag == XFA.IDTAG && idValueMap != null) {
							String sSrcIDValue = srcProto.getAttribute(eTag).toString();
							String sNewIDValue = idValueMap.getPrefix() + ":" + sSrcIDValue;
							setAttribute(new StringAttr(XFA.ID, sNewIDValue), eTag);
						}						
						else if (eTag == XFA.USETAG && idValueMap != null) {
							String sSrcReference = srcProto.getAttribute(eTag).toString();
							if (sSrcReference.length() >= 1 && sSrcReference.charAt(0) == '#') {
								sSrcReference = sSrcReference.substring(1);	// trim the '#'
								String sNewReference = "#" + idValueMap.getPrefix() + ":" + sSrcReference;
								setAttribute(new StringAttr(XFA.USE, sNewReference), eTag);

								// Collect reference so we can verify self-contained-ness
								idValueMap.getReferenceList().add(sSrcReference);
							}
							else {
								// Don't do anything to map SOM-expression-based use references.
								setAttribute(srcProto.getAttribute(eTag), eTag);
							}
						}
						else {
							setAttribute(srcProto.getAttribute(eTag), eTag);
						}

						if (bMarkTransient || bSrcIsExternal) {
							Attribute node = getAttribute(eTag,false,false);
							int index = getAttrIndex(node);
							setAttrProp(index, AttrIsTransient, bMarkTransient);
							setAttrProp(index, AttrIsFragment, bSrcIsExternal);
						}
					}
				}
			}	
		}

		// collect the use attrs of our children, use to ensure we don't add unwanted children
		List useIDs = null;
		for (Node child = getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
		
			// clear transient flag if we are to a reResolve
			if (child.isPropertySpecified(XFA.USEHREFTAG,false,0)) {
				String sUseRef = ((Element)child).getAttribute(XFA.USEHREFTAG,false,false).toString();
				int nSharp = sUseRef.indexOf('#');
				assert(nSharp >= 0);
				if (useIDs == null)
					useIDs = new ArrayList();
				useIDs.add(sUseRef.substring(nSharp + 1));
			}
			else if (child.isPropertySpecified(XFA.USETAG,false, 0)) {
				String sUse = ((Element)child).getAttribute(XFA.USETAG,false,false).toString();
				if (useIDs == null)
					useIDs = new ArrayList();
				useIDs.add(sUse.substring(1));
			}
		}

		// collection of all the children, that need to be proto'd
		NodeList srcProtoChildren;
		
		// get the target nodes
		NodeList targetProtoProperties = enumerateProperties();
		
		if (!bFull && targetProtoProperties == null) {
			srcProtoChildren = srcProto.enumerateChildren();
		}
		else {
			NodeList srcProtoProperties = new ArrayNodeList();
			srcProtoChildren = new ArrayNodeList();			

			srcProto.resolveAndEnumerateChildren(srcProtoProperties, srcProtoChildren, false, false);
			int nSrcProtoProp = srcProtoProperties.length();
			for (int i = 0; i < nSrcProtoProp; i++) {	
				Node srcChild = (Node)srcProtoProperties.item(i);

				// We're not interested in adding  children.
				if (srcChild.isSameClass(XFA.PROTOTAG))
					continue;

				// do we have a explicitly defined proto rel
				if (hasID(srcChild, useIDs))
					continue;

				// look for matching node
				Node targetChild = null;	
				if (targetProtoProperties != null && targetProtoProperties.length() > 0) {
					Integer nLookupIndex = srcProtoProperties.getOccurrence(srcChild);
					targetChild = targetProtoProperties.getNamedItem(srcChild.getPrivateName(), srcChild.getClassAtom(), nLookupIndex.intValue());
				}

				//Support for Assembler: if image contains transferEncoding tag, then following TextNode's relationship should be established
				if (targetChild!=null && targetChild instanceof TextNode &&  srcProto != null && srcProto instanceof ImageValue){
					int index = srcProto.findSchemaAttr(XFA.TRANSFERENCODING); 						
					if (index != -1) targetChild = null;				
				}
				
				boolean bCreate = false;
				if (targetChild == null && bFull)
					bCreate = true;								
				
				establishProtoRelationship(this, srcChild, targetChild, bFull, bCreate, bMarkTransient, bSrcIsExternal);
			}
		}
 
		Element srcOneOfChild = (Element)srcProto.getOneOfChild(true, false);
		if (srcOneOfChild  != null && 
			(bFull || !(srcOneOfChild instanceof Proto)) &&
			!hasID(srcOneOfChild,useIDs)) {
			Element targetOneOfChild = (Element)super.getOneOfChild(true, false);

			boolean bCreate = false;
			if (targetOneOfChild == null && bFull)
				bCreate = true;

			establishProtoRelationship(this, srcOneOfChild, targetOneOfChild, bFull, bCreate, bMarkTransient, bSrcIsExternal);
		}

		int nSrcProtoChildren = srcProtoChildren != null ? srcProtoChildren.length() : 0;
		for (int i = 0; i < nSrcProtoChildren; i++) {
			Node srcChild = (Node)srcProtoChildren.item(i);
			
			// We're not interested in adding  children.
			if (!bFull && srcChild.isSameClass(XFA.PROTOTAG))
				continue;

			// do we have a explicitly defined proto rel
			if (hasID(srcChild,useIDs))
				continue;

			// look for matching node
			Element targetChild = null;	
			NodeList targetProtoChildren = enumerateChildren();
			if (targetProtoChildren != null && targetProtoChildren.length() > 0) {
				Integer nLookupIndex = srcProtoChildren.getOccurrence(srcChild);
				targetChild = (Element)targetProtoChildren.getNamedItem(srcChild.getPrivateName(), srcChild.getClassAtom(), nLookupIndex.intValue());
			}

			establishProtoRelationship(this, (Element)srcChild, targetChild, bFull, true, bMarkTransient, bSrcIsExternal);
		}
		
		// If resolving fragments, perform the update stage of adjusting ID values by calling updateIDValues with
		// the list of changed ID values.
		if (bFull) {
			IDValueMap idValueMap = getModel().getIDValueMap();
			if (idValueMap != null) {
				updateIDValues(idValueMap.getPrefix(), idValueMap.getReferenceList());
			}
		}

		// set lock after children have been modified.
		// if it was locked, or the proto is locked or the parent is locked, set this node to be locked
		if (bWasLocked || srcProto.getLocked() || (getXFAParent() != null && getXFAParent().getLocked()))
			setLocked(true);

//	#if _DEBUG
//		msDebugName = getSomName();
//	#endif
		
		return true;

	}

	/**
	 * @see Element#setElement(Node, int, int)
	 * @exclude from published api.
	 */
	public Node setElement(Node child, int eTag, int nOccurrence) {
		// watson bug 1770168, setElement can replace the child
		child = super.setElement(child, eTag, nOccurrence);
		
		// watson bug 1738663 
		// When setElement is called we must establish the proto relationship to ensure the correct defaults are returned
		if (hasProto() && 
			child instanceof ProtoableNode) {
			
			Node element = mProto.peekElement(eTag, false, nOccurrence);
			if (element != null) {
				
				// get element with peek == false to ensure moProto has the element copied over
				ProtoableNode protoableNode = (ProtoableNode)mProto.getElement(eTag, false, nOccurrence, false, false);
				((ProtoableNode)child).resolveProto(protoableNode, false, false, false);
			}
		}
		
		return child;
	}
	
	/**
	 * @see Element#setOneOfChild(Node)
	 * @exclude from published api.
	 */
	public Node setOneOfChild(Node child) {
		// watson bug 1770168, setOneOfChild can replace the child		
		child = super.setOneOfChild(child);
		
		// watson bug 1738663 
		// When setOneOfChild is called we must establish the proto relationship to ensure the correct defaults are returned
		if (hasProto() && child instanceof ProtoableNode) {
			
			Node oneOf = mProto.getOneOfChild(true, false);
			if (oneOf != null && child.isSameClass(oneOf.getClassTag())) {
				
				// call get oneofchild with peek == false to ensure the node is copied over 
				ProtoableNode oneOfChild = (ProtoableNode)mProto.getOneOfChild(false, false);
				((ProtoableNode)child).resolveProto(oneOfChild, false, false, false);
			}		
		}
		
		return child;
	}
	
	/**
	 * Sets an attribute of this element.
	 *
	 * @param attr
	 *            the attribute.
	 * @param eTag
	 *            The XFA tag name of the attribute being set.
	 */
	public void setAttribute(Attribute attr, int eTag) {
		// first set the property.
		super.setAttribute(attr, eTag);

		// Now check if it's one that affects protos
		if (eTag == XFA.USETAG || eTag == XFA.USEHREFTAG) {
			if (attr == null) {
				// setAttribute removed the attr in this case
				if (eTag == XFA.USETAG && hasProto()) {
					// clear any old protos
					clearProtos();
				}
				else if (eTag == XFA.USEHREFTAG) {
					// clear any old protos -- usehref may refer to an internal proto too
					clearProtos();

					clearExternalProtos();

					mbHasExternalProto = false;

					// need to restore a local proto after clearing external ?
					if (isPropertySpecified(XFA.USETAG, false, 0))
						performResolveProtos(false);
				}
				return;
			} else {
				Model model = getModel();
				if (model.isLoading()) {
					// use nodes are the only nodes that can be added dynamically at load time
					if (eTag == XFA.USETAG)
						model.addUseNode(this);
				}
				else {
					// watson bug 1509171 
					// clear the protos when the use or usehref attributes are updated
					clearProtos();
					clearExternalProtos();

					// add this node to the list of nodes to resolve
					model.addUseNode(this);
					model.addUseHRefNode(this);

					// now resolve this node, we call XFAModelImpl::resolveProtos because this code
					// handles the recursive nuances of proto resolution
					model.resolveProtos(false);
				}
			}
		}
	}
	
	/**
	 * @exclude from published api.
	 */
	public void setPermsLock(boolean bPermsLock) {
		super.setPermsLock(bPermsLock);
		
		// set/clear permissions on the proto
		if (hasProto())
			mProto.setPermsLock(bPermsLock);
	}
	
	/**
	 * @exclude from public api.
	 */
	public void setProto(ProtoableNode refProto) {
	    // remove any existing proto relationship
		if (mProto != null)
			mProto.removeProtoed(this);

		// set the proto relationships
		mProto = refProto;
		refProto.addProtoed(this);

		// copy transparent properties and one of children. Som isn't smart
		// enough to detect protos.
		for (Node child = refProto.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {

			if (child instanceof ProtoableNode) {
				ProtoableNode srcProtoChild = (ProtoableNode) child;

				ChildReln childR = getChildReln(srcProtoChild.getClassTag());
				if (childR.getMax() != ChildReln.UNLIMITED
						&& srcProtoChild.isTransparent()) {
					srcProtoChild.createProto(this, false);
				}
			}
		}
	}

	// jfDomAttr getProtoAttributeNode(String attrName) {
	// // Return the dom attribute node, searching through
	// // any protos as necessary until one is found. Returns
	// // null if not found.
	// // jfDomAttr attr;
	//
	// attr = getAttributeNode(attrName.intern());
	// if (attr != null)
	// return attr;
	// else if (hasProto()) {
	// attr = getProto().getProtoAttributeNode(attrName);
	// }
	// return attr;
	// }


	/**
	 * Static helper method to help prevent memory leaks in the case of circular
	 * fragment references.  Recurses through children clearing moExternalProto (Watson 1610012).
	 * 
 	 * @exclude from published api.
	 */
	public static void releaseExternalProtos(ProtoableNode node) {
		for (Node child = node.getFirstXFAChild(); child != null; child = child.getNextXFASibling()) {
			if (child instanceof ProtoableNode) {
				((ProtoableNode)child).mExternalProto = null;
				releaseExternalProtos((ProtoableNode)child);
			}
		}
		
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy