--- old/src/java.xml/share/classes/com/sun/org/apache/xml/internal/utils/DOM2Helper.java 2017-06-14 09:47:08.116851595 -0700 +++ new/src/java.xml/share/classes/com/sun/org/apache/xml/internal/utils/DOM2Helper.java 2017-06-14 09:47:07.088800244 -0700 @@ -1,6 +1,5 @@ /* - * reserved comment block - * DO NOT REMOVE OR ALTER! + * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -18,298 +17,327 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - package com.sun.org.apache.xml.internal.utils; -import java.io.IOException; - -import javax.xml.parsers.DocumentBuilder; -import javax.xml.parsers.DocumentBuilderFactory; -import javax.xml.parsers.ParserConfigurationException; -import javax.xml.transform.TransformerException; - +import com.sun.org.apache.xml.internal.dtm.ref.DTMNodeProxy; import org.w3c.dom.Attr; -import org.w3c.dom.Document; -import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; import org.w3c.dom.Node; -import org.xml.sax.InputSource; /** - * @deprecated Since the introduction of the DTM, this class will be removed. - * This class provides a DOM level 2 "helper", which provides services currently - * not provided be the DOM standard. + * This class provides a DOM level 2 "helper", which provides several services. + * + * The original class extended DOMHelper that was deprecated and then removed. */ -public class DOM2Helper extends DOMHelper -{ +public final class DOM2Helper { - /** - * Construct an instance. - */ - public DOM2Helper(){} - - /** - * Check node to see if it was created by a DOM implementation - * that this helper is intended to support. This is currently - * disabled, and assumes all nodes are acceptable rather than checking - * that they implement com.sun.org.apache.xerces.internal.dom.NodeImpl. - * - * @param node The node to be tested. - * - * @throws TransformerException if the node is not one which this - * DOM2Helper can support. If we return without throwing the exception, - * the node is compatable. - * @xsl.usage internal - */ - public void checkNode(Node node) throws TransformerException - { - - // if(!(node instanceof com.sun.org.apache.xerces.internal.dom.NodeImpl)) - // throw new TransformerException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_XERCES_CANNOT_HANDLE_NODES, new Object[]{((Object)node).getClass()})); //"DOM2Helper can not handle nodes of type" - //+((Object)node).getClass()); - } - - /** - * Returns true if the DOM implementation handled by this helper - * supports the SAX ContentHandler interface. - * - * @return true (since Xerces does). - */ - public boolean supportsSAX() - { - return true; - } - - /** Field m_doc: Document Node for the document this helper is currently - * accessing or building - * @see #setDocument - * @see #getDocument - * */ - private Document m_doc; - - /** - * Specify which document this helper is currently operating on. - * - * @param doc The DOM Document node for this document. - * @see #getDocument - */ - public void setDocument(Document doc) - { - m_doc = doc; - } - - /** - * Query which document this helper is currently operating on. - * - * @return The DOM Document node for this document. - * @see #setDocument - */ - public Document getDocument() - { - return m_doc; - } - - /** - * Parse an XML document. - * - *

Right now the Xerces DOMParser class is used. This needs - * fixing, either via jaxp, or via some other, standard method.

- * - *

The application can use this method to instruct the SAX parser - * to begin parsing an XML document from any valid input - * source (a character stream, a byte stream, or a URI).

- * - *

Applications may not invoke this method while a parse is in - * progress (they should create a new Parser instead for each - * additional XML document). Once a parse is complete, an - * application may reuse the same Parser object, possibly with a - * different input source.

- * - * @param source The input source for the top-level of the - * XML document. - * - * @throws TransformerException if any checked exception is thrown. - * @xsl.usage internal - */ - public void parse(InputSource source) throws TransformerException - { - - try - { - - // I guess I should use JAXP factory here... when it's legal. - // com.sun.org.apache.xerces.internal.parsers.DOMParser parser - // = new com.sun.org.apache.xerces.internal.parsers.DOMParser(); - DocumentBuilderFactory builderFactory = - DocumentBuilderFactory.newInstance(); - - builderFactory.setNamespaceAware(true); - builderFactory.setValidating(true); - - DocumentBuilder parser = builderFactory.newDocumentBuilder(); - - /* - // domParser.setFeature("http://apache.org/xml/features/dom/create-entity-ref-nodes", getShouldExpandEntityRefs()? false : true); - if(m_useDOM2getNamespaceURI) - { - parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", true); - parser.setFeature("http://xml.org/sax/features/namespaces", true); - } - else - { - parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false); - } - - parser.setFeature("http://apache.org/xml/features/allow-java-encodings", true); - */ - - parser.setErrorHandler( - new com.sun.org.apache.xml.internal.utils.DefaultErrorHandler()); - - // if(null != m_entityResolver) - // { - // System.out.println("Setting the entity resolver."); - // parser.setEntityResolver(m_entityResolver); - // } - setDocument(parser.parse(source)); + /** + * Construct an instance. + */ + private DOM2Helper() { } - catch (org.xml.sax.SAXException se) - { - throw new TransformerException(se); - } - catch (ParserConfigurationException pce) - { - throw new TransformerException(pce); - } - catch (IOException ioe) - { - throw new TransformerException(ioe); + + /** + * Returns the local name of the given node, as defined by the XML + * Namespaces specification. This is prepared to handle documents built + * using DOM Level 1 methods by falling back upon explicitly parsing the + * node name. + * + * @param n Node to be examined + * + * @return String containing the local name, or null if the node was not + * assigned a Namespace. + */ + public static String getLocalNameOfNode(Node n) { + + String name = n.getLocalName(); + + return (null == name) ? getLocalNameOfNodeFallback(n) : name; } - // setDocument(((com.sun.org.apache.xerces.internal.parsers.DOMParser)parser).getDocument()); - } + /** + * Returns the local name of the given node. If the node's name begins with + * a namespace prefix, this is the part after the colon; otherwise it's the + * full node name. + * + * This method is copied from + * com.sun.org.apache.xml.internal.utils.DOMHelper + * + * @param n the node to be examined. + * + * @return String containing the Local Name + */ + private static String getLocalNameOfNodeFallback(Node n) { - /** - * Given an XML ID, return the element. This requires assistance from the - * DOM and parser, and is meaningful only in the context of a DTD - * or schema which declares attributes as being of type ID. This - * information may or may not be available in all parsers, may or - * may not be available for specific documents, and may or may not - * be available when validation is not turned on. - * - * @param id The ID to search for, as a String. - * @param doc The document to search within, as a DOM Document node. - * @return DOM Element node with an attribute of type ID whose value - * uniquely matches the requested id string, or null if there isn't - * such an element or if the DOM can't answer the question for other - * reasons. - */ - public Element getElementByID(String id, Document doc) - { - return doc.getElementById(id); - } - - /** - * Figure out whether node2 should be considered as being later - * in the document than node1, in Document Order as defined - * by the XPath model. This may not agree with the ordering defined - * by other XML applications. - *

- * There are some cases where ordering isn't defined, and neither are - * the results of this function -- though we'll generally return true. - *

- * TODO: Make sure this does the right thing with attribute nodes!!! - * - * @param node1 DOM Node to perform position comparison on. - * @param node2 DOM Node to perform position comparison on . - * - * @return false if node2 comes before node1, otherwise return true. - * You can think of this as - * (node1.documentOrderPosition <= node2.documentOrderPosition). - */ - public static boolean isNodeAfter(Node node1, Node node2) - { - - // Assume first that the nodes are DTM nodes, since discovering node - // order is massivly faster for the DTM. - if(node1 instanceof DOMOrder && node2 instanceof DOMOrder) - { - int index1 = ((DOMOrder) node1).getUid(); - int index2 = ((DOMOrder) node2).getUid(); + String qname = n.getNodeName(); + int index = qname.indexOf(':'); + + return (index < 0) ? qname : qname.substring(index + 1); + } - return index1 <= index2; + /** + * Returns the Namespace Name (Namespace URI) for the given node. In a Level + * 2 DOM, you can ask the node itself. Note, however, that doing so + * conflicts with our decision in getLocalNameOfNode not to trust the that + * the DOM was indeed created using the Level 2 methods. If Level 1 methods + * were used, these two functions will disagree with each other. + *

+ * TODO: Reconcile with getLocalNameOfNode. + * + * @param n Node to be examined + * + * @return String containing the Namespace URI bound to this DOM node at the + * time the Node was created. + */ + public static String getNamespaceOfNode(Node n) { + return n.getNamespaceURI(); } - else - { - // isNodeAfter will return true if node is after countedNode - // in document order. The base isNodeAfter is sloooow (relatively). - return DOMHelper.isNodeAfter(node1, node2); + /** + * Figure out whether node2 should be considered as being later in the + * document than node1, in Document Order as defined by the XPath model. + * This may not agree with the ordering defined by other XML applications. + *

+ * There are some cases where ordering isn't defined, and neither are the + * results of this function -- though we'll generally return true. + * + * @param node1 DOM Node to perform position comparison on. + * @param node2 DOM Node to perform position comparison on . + * + * @return false if node2 comes before node1, otherwise return true. You can + * think of this as + * {@code (node1.documentOrderPosition <= node2.documentOrderPosition)}. + */ + public static boolean isNodeAfter(Node node1, Node node2) { + if (node1 == node2 || isNodeTheSame(node1, node2)) { + return true; + } + + // Default return value, if there is no defined ordering + boolean isNodeAfter = true; + + Node parent1 = getParentOfNode(node1); + Node parent2 = getParentOfNode(node2); + + // Optimize for most common case + if (parent1 == parent2 || isNodeTheSame(parent1, parent2)) // then we know they are siblings + { + if (null != parent1) { + isNodeAfter = isNodeAfterSibling(parent1, node1, node2); + } + } else { + // General strategy: Figure out the lengths of the two + // ancestor chains, reconcile the lengths, and look for + // the lowest common ancestor. If that ancestor is one of + // the nodes being compared, it comes before the other. + // Otherwise perform a sibling compare. + // + // NOTE: If no common ancestor is found, ordering is undefined + // and we return the default value of isNodeAfter. + // Count parents in each ancestor chain + int nParents1 = 2, nParents2 = 2; // include node & parent obtained above + + while (parent1 != null) { + nParents1++; + + parent1 = getParentOfNode(parent1); + } + + while (parent2 != null) { + nParents2++; + + parent2 = getParentOfNode(parent2); + } + + // Initially assume scan for common ancestor starts with + // the input nodes. + Node startNode1 = node1, startNode2 = node2; + + // If one ancestor chain is longer, adjust its start point + // so we're comparing at the same depths + if (nParents1 < nParents2) { + // Adjust startNode2 to depth of startNode1 + int adjust = nParents2 - nParents1; + + for (int i = 0; i < adjust; i++) { + startNode2 = getParentOfNode(startNode2); + } + } else if (nParents1 > nParents2) { + // adjust startNode1 to depth of startNode2 + int adjust = nParents1 - nParents2; + + for (int i = 0; i < adjust; i++) { + startNode1 = getParentOfNode(startNode1); + } + } + + Node prevChild1 = null, prevChild2 = null; // so we can "back up" + + // Loop up the ancestor chain looking for common parent + while (null != startNode1) { + if (startNode1 == startNode2 || isNodeTheSame(startNode1, startNode2)) // common parent? + { + if (null == prevChild1) // first time in loop? + { + + // Edge condition: one is the ancestor of the other. + isNodeAfter = (nParents1 < nParents2) ? true : false; + + break; // from while loop + } else { + // Compare ancestors below lowest-common as siblings + isNodeAfter = isNodeAfterSibling(startNode1, prevChild1, + prevChild2); + + break; // from while loop + } + } // end if(startNode1 == startNode2) + + // Move up one level and try again + prevChild1 = startNode1; + startNode1 = getParentOfNode(startNode1); + prevChild2 = startNode2; + startNode2 = getParentOfNode(startNode2); + } // end while(parents exist to examine) + } // end big else (not immediate siblings) + + return isNodeAfter; + } // end isNodeAfter(Node node1, Node node2) + + /** + * Use DTMNodeProxy to determine whether two nodes are the same. + * + * @param node1 The first DOM node to compare. + * @param node2 The second DOM node to compare. + * @return true if the two nodes are the same. + */ + public static boolean isNodeTheSame(Node node1, Node node2) { + if (node1 instanceof DTMNodeProxy && node2 instanceof DTMNodeProxy) { + return ((DTMNodeProxy) node1).equals((DTMNodeProxy) node2); + } else { + return (node1 == node2); + } } - } - /** - * Get the XPath-model parent of a node. This version takes advantage - * of the DOM Level 2 Attr.ownerElement() method; the base version we - * would otherwise inherit is prepared to fall back on exhaustively - * walking the document to find an Attr's parent. - * - * @param node Node to be examined - * - * @return the DOM parent of the input node, if there is one, or the - * ownerElement if the input node is an Attr, or null if the node is - * a Document, a DocumentFragment, or an orphan. - */ - public static Node getParentOfNode(Node node) - { - Node parent=node.getParentNode(); - if(parent==null && (Node.ATTRIBUTE_NODE == node.getNodeType()) ) - parent=((Attr) node).getOwnerElement(); - return parent; - } - - /** - * Returns the local name of the given node, as defined by the - * XML Namespaces specification. This is prepared to handle documents - * built using DOM Level 1 methods by falling back upon explicitly - * parsing the node name. - * - * @param n Node to be examined - * - * @return String containing the local name, or null if the node - * was not assigned a Namespace. - */ - public String getLocalNameOfNode(Node n) - { - - String name = n.getLocalName(); - - return (null == name) ? super.getLocalNameOfNode(n) : name; - } - - /** - * Returns the Namespace Name (Namespace URI) for the given node. - * In a Level 2 DOM, you can ask the node itself. Note, however, that - * doing so conflicts with our decision in getLocalNameOfNode not - * to trust the that the DOM was indeed created using the Level 2 - * methods. If Level 1 methods were used, these two functions will - * disagree with each other. - *

- * TODO: Reconcile with getLocalNameOfNode. - * - * @param n Node to be examined - * - * @return String containing the Namespace URI bound to this DOM node - * at the time the Node was created. - */ - public String getNamespaceOfNode(Node n) - { - return n.getNamespaceURI(); - } - - /** Field m_useDOM2getNamespaceURI is a compile-time flag which - * gates some of the parser options used to build a DOM -- but - * that code is commented out at this time and nobody else - * references it, so I've commented this out as well. */ - //private boolean m_useDOM2getNamespaceURI = false; + /** + * Get the XPath-model parent of a node. This version takes advantage of the + * DOM Level 2 Attr.ownerElement() method; the base version we would + * otherwise inherit is prepared to fall back on exhaustively walking the + * document to find an Attr's parent. + * + * @param node Node to be examined + * + * @return the DOM parent of the input node, if there is one, or the + * ownerElement if the input node is an Attr, or null if the node is a + * Document, a DocumentFragment, or an orphan. + */ + public static Node getParentOfNode(Node node) { + Node parent = node.getParentNode(); + if (parent == null && (Node.ATTRIBUTE_NODE == node.getNodeType())) { + parent = ((Attr) node).getOwnerElement(); + } + return parent; + } + + /** + * Figure out if child2 is after child1 in document order. + *

+ * Warning: Some aspects of "document order" are not well defined. For + * example, the order of attributes is considered meaningless in XML, and + * the order reported by our model will be consistent for a given invocation + * but may not match that of either the source file or the serialized + * output. + * + * @param parent Must be the parent of both child1 and child2. + * @param child1 Must be the child of parent and not equal to child2. + * @param child2 Must be the child of parent and not equal to child1. + * @return true if child 2 is after child1 in document order. + */ + private static boolean isNodeAfterSibling(Node parent, Node child1, + Node child2) { + + boolean isNodeAfterSibling = false; + short child1type = child1.getNodeType(); + short child2type = child2.getNodeType(); + + if ((Node.ATTRIBUTE_NODE != child1type) + && (Node.ATTRIBUTE_NODE == child2type)) { + + // always sort attributes before non-attributes. + isNodeAfterSibling = false; + } else if ((Node.ATTRIBUTE_NODE == child1type) + && (Node.ATTRIBUTE_NODE != child2type)) { + + // always sort attributes before non-attributes. + isNodeAfterSibling = true; + } else if (Node.ATTRIBUTE_NODE == child1type) { + NamedNodeMap children = parent.getAttributes(); + int nNodes = children.getLength(); + boolean found1 = false, found2 = false; + + // Count from the start until we find one or the other. + for (int i = 0; i < nNodes; i++) { + Node child = children.item(i); + + if (child1 == child || isNodeTheSame(child1, child)) { + if (found2) { + isNodeAfterSibling = false; + + break; + } + + found1 = true; + } else if (child2 == child || isNodeTheSame(child2, child)) { + if (found1) { + isNodeAfterSibling = true; + + break; + } + + found2 = true; + } + } + } else { + // TODO: Check performance of alternate solution: + // There are two choices here: Count from the start of + // the document until we find one or the other, or count + // from one until we find or fail to find the other. + // Either can wind up scanning all the siblings in the worst + // case, which on a wide document can be a lot of work but + // is more typically is a short list. + // Scanning from the start involves two tests per iteration, + // but it isn't clear that scanning from the middle doesn't + // yield more iterations on average. + // We should run some testcases. + Node child = parent.getFirstChild(); + boolean found1 = false, found2 = false; + + while (null != child) { + + // Node child = children.item(i); + if (child1 == child || isNodeTheSame(child1, child)) { + if (found2) { + isNodeAfterSibling = false; + + break; + } + + found1 = true; + } else if (child2 == child || isNodeTheSame(child2, child)) { + if (found1) { + isNodeAfterSibling = true; + + break; + } + + found2 = true; + } + + child = child.getNextSibling(); + } + } + + return isNodeAfterSibling; + } // end isNodeAfterSibling(Node parent, Node child1, Node child2) }