| /* |
| * Copyright (c) 1998, 2019 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. |
| * |
| * @param marshalRecord |
| * @param object |
| * @param session |
| * @param namespaceResolver |
| * @param marshaller |
| * @return |
| */ |
| 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(); |
| } |
| |
| } |