blob: a025d2df3fdf643104967a7de80f3084b6fa42f8 [file] [log] [blame]
/*
* Copyright (c) 1998, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.oxm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.eclipse.persistence.core.queries.CoreAttributeGroup;
import org.eclipse.persistence.internal.core.sessions.CoreAbstractSession;
import org.eclipse.persistence.internal.oxm.mappings.Mapping;
import org.eclipse.persistence.internal.oxm.record.MarshalContext;
import org.eclipse.persistence.internal.oxm.record.MarshalRecord;
import org.eclipse.persistence.internal.oxm.record.ObjectMarshalContext;
/**
* INTERNAL:
* <p><b>Purpose</b>: XPathNodes are used together to build a tree. The tree
* is built from all of the XPath statements specified in the mapping metadata
* (mappings and policies). This tree is then navigated by an
* EventObjectBuilder to perform marshal and unmarshal operations.</p>
* <p>The XPaths "a/b" and "a/c" would result in a tree with the root "a" and
* two child nodes "b" and "c".</p>
* <p><b>Responsibilities</b>:<ul>
* <li>All tree relationships must be bi-directional.</li>
* <li>Reference a NodeValue, XPathNodes without a Node value represent grouping
* elements.</li>
* <li>Reference an XPathFragment, XPathFragments contain name and namespace
* information.</li>
* <li>Must differentiate between child nodes that correspond to elements and
* those that do not.</li>
* <li>Must represent special mapping situations like any and self mappings.</li>
* </ul>
*/
public class XPathNode {
private NodeValue unmarshalNodeValue;
private NodeValue marshalNodeValue;
private boolean isMarshalOnlyNodeValue;
private XPathFragment xPathFragment;
private XPathNode parent;
private List<XPathNode> attributeChildren;
private List<XPathNode> nonAttributeChildren;
private List<XPathNode> selfChildren;
private Map<XPathFragment, XPathNode> attributeChildrenMap;
private Map<String, XPathNode> attributeChildrenLookupTable;
private boolean isAttributeChildrenLookupTableFilled = false;
private Map<XPathFragment, XPathNode> nonAttributeChildrenMap;
private Map<String, XPathNode> nonAttributeChildrenLookupTable;
private boolean isNonAttributeChildrenLookupTableFilled = false;
private MappingNodeValue anyAttributeNodeValue;
private XPathNode anyAttributeNode;
private XPathNode textNode;
private XPathNode anyNode;
private XPathNode nextNode;
private boolean hasTypeChild;
private boolean hasPredicateSiblings;
private boolean hasPredicateChildren;
private NullCapableValue nullCapableValue;
public XPathFragment getXPathFragment() {
return xPathFragment;
}
public void setXPathFragment(XPathFragment xPathFragment) {
this.xPathFragment = xPathFragment;
}
public NodeValue getNodeValue() {
return unmarshalNodeValue;
}
public void setNodeValue(NodeValue nodeValue) {
this.marshalNodeValue = nodeValue;
this.unmarshalNodeValue = nodeValue;
if (null != nodeValue) {
nodeValue.setXPathNode(this);
isMarshalOnlyNodeValue = nodeValue.isMarshalOnlyNodeValue();
}
}
public NodeValue getUnmarshalNodeValue() {
return unmarshalNodeValue;
}
public void setUnmarshalNodeValue(NodeValue nodeValue) {
if (null != nodeValue) {
nodeValue.setXPathNode(this);
}
this.unmarshalNodeValue = nodeValue;
}
public NodeValue getMarshalNodeValue() {
return marshalNodeValue;
}
public void setMarshalNodeValue(NodeValue nodeValue) {
if (null != nodeValue) {
nodeValue.setXPathNode(this);
}
this.marshalNodeValue = nodeValue;
isMarshalOnlyNodeValue = marshalNodeValue.isMarshalOnlyNodeValue();
}
public NullCapableValue getNullCapableValue() {
return nullCapableValue;
}
public void setNullCapableValue(NullCapableValue nullCapableValue) {
this.nullCapableValue = nullCapableValue;
}
public XPathNode getParent() {
return parent;
}
public void setParent(XPathNode parent) {
this.parent = parent;
}
public List<XPathNode> getAttributeChildren() {
return this.attributeChildren;
}
public List<XPathNode> getNonAttributeChildren() {
return this.nonAttributeChildren;
}
public List<XPathNode> getSelfChildren() {
return this.selfChildren;
}
public Map<XPathFragment, XPathNode> getNonAttributeChildrenMap() {
return this.nonAttributeChildrenMap;
}
public Map<XPathFragment, XPathNode> getAttributeChildrenMap() {
return this.attributeChildrenMap;
}
public boolean isChildrenLookupTableFilled(boolean isAttribute) {
return isAttribute ? isAttributeChildrenLookupTableFilled : isNonAttributeChildrenLookupTableFilled;
}
public void setChildrenLookupTableFilled(boolean isAttribute) {
if (isAttribute)
this.isAttributeChildrenLookupTableFilled = true;
else
this.isNonAttributeChildrenLookupTableFilled = true;
}
public Map<String, XPathNode> getChildrenLookupTable(boolean isAttribute) {
return isAttribute ? getAttributeChildrenLookupTable() : getNonAttributeChildrenLookupTable();
}
private Map<String, XPathNode> getAttributeChildrenLookupTable() {
if (attributeChildrenLookupTable == null)
attributeChildrenLookupTable = new HashMap<>();
return attributeChildrenLookupTable;
}
private Map<String, XPathNode> getNonAttributeChildrenLookupTable() {
if (nonAttributeChildrenLookupTable == null)
nonAttributeChildrenLookupTable = new HashMap<>();
return nonAttributeChildrenLookupTable;
}
public void setAnyAttributeNodeValue(MappingNodeValue nodeValue) {
this.anyAttributeNodeValue = nodeValue;
}
public MappingNodeValue getAnyAttributeNodeValue() {
return this.anyAttributeNodeValue;
}
public XPathNode getAnyAttributeNode() {
return this.anyAttributeNode;
}
public XPathNode getAnyNode() {
return this.anyNode;
}
public void setAnyNode(XPathNode xPathNode) {
this.anyNode = xPathNode;
}
public XPathNode getNextNode() {
return nextNode;
}
public XPathNode getTextNode() {
return this.textNode;
}
public void setTextNode(XPathNode xPathNode) {
this.textNode = xPathNode;
}
public boolean hasTypeChild() {
return hasTypeChild;
}
@Override
public boolean equals(Object object) {
try {
XPathFragment perfNodeXPathFragment = ((XPathNode)object).getXPathFragment();
if(xPathFragment == perfNodeXPathFragment) {
return true;
} else if(null == xPathFragment) {
return false;
} else if(null == perfNodeXPathFragment) {
return false;
}
return xPathFragment.equals(perfNodeXPathFragment);
// turn fix off for now until we re-enable XMLAnyObjectAndAnyCollectionTestCases
// } catch (NullPointerException npe) {
// b5259059 all cases X0X1 (1mapping xpath=null, 2nd mapping xpath=filled
// catch when object.getXPathFragment() == null
// (this will also catch case where perfNode XPath is null)
// return false;
} catch (ClassCastException e) {
return false;
}
}
@Override
public int hashCode() {
return xPathFragment != null ? xPathFragment.hashCode() : 0;
}
public XPathNode addChild(XPathFragment anXPathFragment, NodeValue aNodeValue, NamespaceResolver namespaceResolver) {
if (null != anXPathFragment && anXPathFragment.nameIsText()) {
if (aNodeValue.isOwningNode(anXPathFragment)) {
XPathNode textXPathNode = this.getTextNode();
if(textXPathNode == null) {
textXPathNode = new XPathNode();
}
textXPathNode.setParent(this);
textXPathNode.setXPathFragment(anXPathFragment);
if (aNodeValue.isMarshalNodeValue()) {
textXPathNode.setMarshalNodeValue(aNodeValue);
}
if (aNodeValue.isUnmarshalNodeValue()) {
textXPathNode.setUnmarshalNodeValue(aNodeValue);
}
this.setTextNode(textXPathNode);
if(null != nonAttributeChildren && !nonAttributeChildren.contains(textXPathNode)) {
nonAttributeChildren.add(textXPathNode);
}
if(aNodeValue instanceof XMLCompositeObjectMappingNodeValue) {
if (null == selfChildren) {
selfChildren = new ArrayList<>();
}
selfChildren.add(textXPathNode);
}
return textXPathNode;
}
}
if (anXPathFragment != null && namespaceResolver != null && anXPathFragment.getNamespaceURI() == null && !anXPathFragment.nameIsText()) {
if(!anXPathFragment.isAttribute()) {
anXPathFragment.setNamespaceURI(namespaceResolver.resolveNamespacePrefix(anXPathFragment.getPrefix()));
} else if(anXPathFragment.hasNamespace()) {
anXPathFragment.setNamespaceURI(namespaceResolver.resolveNamespacePrefix(anXPathFragment.getPrefix()));
}
}
XPathNode xPathNode = new XPathNode();
xPathNode.setXPathFragment(anXPathFragment);
List<XPathNode> children;
Map childrenMap;
if ((anXPathFragment != null) && anXPathFragment.isAttribute()) {
if (null == attributeChildren) {
attributeChildren = new ArrayList();
}
if (null == attributeChildrenMap) {
attributeChildrenMap = new HashMap();
}
children = attributeChildren;
childrenMap = attributeChildrenMap;
} else {
if (null == nonAttributeChildren) {
nonAttributeChildren = new ArrayList();
if(null != textNode) {
nonAttributeChildren.add(textNode);
}
}
if (null == nonAttributeChildrenMap) {
//The reason behind LinkedHashMap is the order of items when for-cycling HashMap.getEntrySet() or HashMap.getKeySet().
//This change fixes non-determinism (implementation in JDK8 has changed so the order is different (sometimes) than in JDK6 and JDK7).
nonAttributeChildrenMap = new LinkedHashMap();
}
if(anXPathFragment !=null && Constants.SCHEMA_TYPE_ATTRIBUTE.equals(anXPathFragment.getLocalName())){
hasTypeChild = true;
}
children = nonAttributeChildren;
childrenMap = nonAttributeChildrenMap;
}
if (null == anXPathFragment) {
if(aNodeValue.isMarshalNodeValue()) {
xPathNode.setMarshalNodeValue(aNodeValue);
}
if(aNodeValue.isUnmarshalNodeValue() && xPathNode.getUnmarshalNodeValue() == null) {
xPathNode.setUnmarshalNodeValue(aNodeValue);
}
xPathNode.setParent(this);
if (aNodeValue instanceof XMLAnyAttributeMappingNodeValue || (aNodeValue instanceof XMLVariableXPathObjectMappingNodeValue && ((XMLVariableXPathObjectMappingNodeValue)aNodeValue).getMapping().isAttribute() ) || (aNodeValue instanceof XMLVariableXPathCollectionMappingNodeValue && ((XMLVariableXPathCollectionMappingNodeValue)aNodeValue).getMapping().isAttribute() )) {
setAnyAttributeNodeValue((MappingNodeValue)aNodeValue);
anyAttributeNode = xPathNode;
} else {
if(!children.contains(xPathNode)) {
children.add(xPathNode);
}
setAnyNode(xPathNode);
}
return xPathNode;
}
this.hasPredicateChildren = hasPredicateChildren || anXPathFragment.getPredicate() != null;
if(this.getNonAttributeChildren() != null && this.hasPredicateChildren) {
for(XPathNode nextChild: this.getNonAttributeChildren()) {
XPathFragment nextFrag = nextChild.getXPathFragment();
if(nextFrag != null && nextFrag.equals(anXPathFragment, true)) {
if(nextFrag.getPredicate() == null && anXPathFragment.getPredicate() != null) {
nextChild.setHasPredicateSiblings(true);
} else if(anXPathFragment.getPredicate() == null && nextFrag.getPredicate() != null) {
xPathNode.setHasPredicateSiblings(true);
}
}
}
}
boolean isSelfFragment = XPathFragment.SELF_FRAGMENT.equals(anXPathFragment);
if(isSelfFragment){
children.add(xPathNode);
if (null == selfChildren) {
selfChildren = new ArrayList<>();
}
selfChildren.add(xPathNode);
}else{
int index = children.indexOf(xPathNode);
if (index >= 0) {
xPathNode = children.get(index);
} else {
xPathNode.setParent(this);
if(!children.contains(xPathNode)) {
int childrenSize = children.size();
if (childrenSize > 0) {
children.get(childrenSize - 1).nextNode = xPathNode;
}
children.add(xPathNode);
}
childrenMap.put(anXPathFragment, xPathNode);
}
}
if (aNodeValue.isOwningNode(anXPathFragment)) {
if(aNodeValue.isMarshalNodeValue()) {
xPathNode.setMarshalNodeValue(aNodeValue);
}
if(aNodeValue.isUnmarshalNodeValue() && xPathNode.getUnmarshalNodeValue() == null) {
xPathNode.setUnmarshalNodeValue(aNodeValue);
}
} else {
XPathFragment nextFragment = anXPathFragment.getNextFragment();
xPathNode.addChild(nextFragment, aNodeValue, namespaceResolver);
}
return xPathNode;
}
private void setHasPredicateSiblings(boolean b) {
this.hasPredicateSiblings = b;
}
public boolean hasPredicateSiblings() {
return this.hasPredicateSiblings;
}
public boolean marshal(MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver, Marshaller marshaller, MarshalContext marshalContext, XPathFragment rootFragment) {
if ((null == marshalNodeValue) || isMarshalOnlyNodeValue) {
if(marshalRecord.isWrapperAsCollectionName() && null != nonAttributeChildren && nonAttributeChildren.size() == 1) {
XPathNode childXPathNode = nonAttributeChildren.get(0);
NodeValue childXPathNodeUnmarshalNodeValue = childXPathNode.getUnmarshalNodeValue();
if(childXPathNodeUnmarshalNodeValue != null && childXPathNodeUnmarshalNodeValue.isContainerValue()) {
ContainerValue containerValue = (ContainerValue) childXPathNodeUnmarshalNodeValue;
if(containerValue.isWrapperAllowedAsCollectionName()) {
XPathNode wrapperXPathNode = new XPathNode();
wrapperXPathNode.setXPathFragment(this.getXPathFragment());
wrapperXPathNode.setMarshalNodeValue(childXPathNode.getMarshalNodeValue());
return wrapperXPathNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, marshalContext, rootFragment);
}
}
}
marshalRecord.addGroupingElement(this);
boolean hasValue = false;
if (null != attributeChildren) {
for (int x = 0, size = attributeChildren.size(); x < size; x++) {
XPathNode xPathNode = attributeChildren.get(x);
hasValue = xPathNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, ObjectMarshalContext.getInstance(), this.xPathFragment) || hasValue;
}
}
if (anyAttributeNode != null) {
hasValue = anyAttributeNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, ObjectMarshalContext.getInstance(), null) || hasValue;
}
if (null == nonAttributeChildren) {
if (textNode != null) {
hasValue = textNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, ObjectMarshalContext.getInstance(), null) || hasValue;
}
} else {
for (int x = 0, size = marshalContext.getNonAttributeChildrenSize(this); x < size; x++) {
XPathNode xPathNode = (XPathNode)marshalContext.getNonAttributeChild(x, this);
MarshalContext childMarshalContext = marshalContext.getMarshalContext(x);
hasValue = xPathNode.marshal(marshalRecord, object, session, namespaceResolver, marshaller, childMarshalContext, this.xPathFragment) || hasValue;
}
}
if (hasValue) {
marshalRecord.endElement(xPathFragment, namespaceResolver);
} else {
marshalRecord.removeGroupingElement(this);
}
return hasValue;
} else {
if(marshalNodeValue.isMappingNodeValue()) {
Mapping mapping = ((MappingNodeValue)marshalNodeValue).getMapping();
CoreAttributeGroup currentGroup = marshalRecord.getCurrentAttributeGroup();
if(!(currentGroup.containsAttributeInternal(mapping.getAttributeName()))) {
return false;
}
}
return marshalContext.marshal(marshalNodeValue, xPathFragment, marshalRecord, object, session, namespaceResolver, rootFragment);
}
}
public boolean startElement(MarshalRecord marshalRecord, XPathFragment anXPathFragment, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver, ObjectBuilder compositeObjectBuilder, Object compositeObject) {
if (null == anXPathFragment) {
return false;
}
marshalRecord.openStartElement(anXPathFragment, namespaceResolver);
boolean hasValue = false;
marshalRecord.predicateAttribute(anXPathFragment, namespaceResolver);
if (null != attributeChildren) {
for (int x = 0, size = attributeChildren.size(); x < size; x++) {
XPathNode attributeNode = attributeChildren.get(x);
hasValue = attributeNode.marshal(marshalRecord, object, session, namespaceResolver, null, ObjectMarshalContext.getInstance(), null) || hasValue;
}
}
if (anyAttributeNode != null) {
//marshal the anyAttribute node here before closeStartElement()
hasValue = anyAttributeNode.marshal(marshalRecord, object, session, namespaceResolver, null, ObjectMarshalContext.getInstance(), null) || hasValue;
}
if (null != compositeObjectBuilder) {
hasValue = compositeObjectBuilder.marshalAttributes(marshalRecord, compositeObject, session) || hasValue;
}
marshalRecord.closeStartElement();
return hasValue;
}
/**
* Marshal any 'self' mapped attributes.
*
*/
public boolean marshalSelfAttributes(MarshalRecord marshalRecord, Object object, CoreAbstractSession session, NamespaceResolver namespaceResolver, Marshaller marshaller) {
if (marshalNodeValue == null) {
return false;
}
return marshalNodeValue.marshalSelfAttributes(xPathFragment, marshalRecord, object, session, namespaceResolver, marshaller);
}
public boolean isWhitespaceAware() {
return unmarshalNodeValue.isWhitespaceAware();
}
}