com.adobe.xfa.ProtoableNode Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of aem-sdk-api Show documentation
Show all versions of aem-sdk-api Show documentation
The Adobe Experience Manager SDK
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);
}
}
}
}