diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java index aa8eeb170d3..d8dc27c0651 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSet.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -55,7 +55,7 @@ import org.w3c.dom.traversal.NodeIterator; * to the same calls; the disadvantage is that some of them may return * less-than-enlightening results when you do so.

* @xsl.usage advanced - * @LastModified: Nov 2017 + * @LastModified: May 2022 */ public class NodeSet implements NodeList, NodeIterator, Cloneable, ContextNodeList @@ -379,11 +379,7 @@ public class NodeSet */ public void addNode(Node n) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - - this.addElement(n); + addElement(n); } /** @@ -397,10 +393,6 @@ public class NodeSet */ public void insertNode(Node n, int pos) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - insertElementAt(n, pos); } @@ -413,11 +405,7 @@ public class NodeSet */ public void removeNode(Node n) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - - this.removeElement(n); + removeElement(n); } /** @@ -431,10 +419,6 @@ public class NodeSet */ public void addNodes(NodeList nodelist) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - if (null != nodelist) // defensive to fix a bug that Sanjiva reported. { int nChildren = nodelist.getLength(); @@ -471,10 +455,6 @@ public class NodeSet */ public void addNodes(NodeSet ns) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - addNodes((NodeIterator) ns); } @@ -488,10 +468,6 @@ public class NodeSet */ public void addNodes(NodeIterator iterator) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - if (null != iterator) // defensive to fix a bug that Sanjiva reported. { Node obj; @@ -516,10 +492,6 @@ public class NodeSet */ public void addNodesInDocOrder(NodeList nodelist, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - int nChildren = nodelist.getLength(); for (int i = 0; i < nChildren; i++) @@ -544,10 +516,6 @@ public class NodeSet */ public void addNodesInDocOrder(NodeIterator iterator, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - Node node; while (null != (node = iterator.nextNode())) @@ -572,10 +540,6 @@ public class NodeSet private boolean addNodesInDocOrder(int start, int end, int testIndex, NodeList nodelist, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - boolean foundit = false; int i; Node node = nodelist.item(testIndex); @@ -632,10 +596,6 @@ public class NodeSet */ public int addNodeInDocOrder(Node node, boolean test, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - int insertIndex = -1; if (test) @@ -706,10 +666,6 @@ public class NodeSet */ public int addNodeInDocOrder(Node node, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - return addNodeInDocOrder(node, true, support); } // end addNodeInDocOrder(Vector v, Object obj) @@ -767,9 +723,6 @@ public class NodeSet return n; } - /** True if this list can be mutated. */ - transient protected boolean m_mutable = true; - /** True if this list is cached. * @serial */ transient protected boolean m_cacheNodes = true; @@ -804,7 +757,6 @@ public class NodeSet XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANNOT_CALL_SETSHOULDCACHENODE, null)); //"Can not call setShouldCacheNodes after nextNode has been called!"); m_cacheNodes = b; - m_mutable = true; } @@ -875,9 +827,6 @@ public class NodeSet */ public void addElement(Node value) { - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - if ((m_firstFree + 1) >= m_mapSize) { if (null == m_map) @@ -1102,9 +1051,6 @@ public class NodeSet */ public void insertElementAt(Node value, int at) { - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - if (null == m_map) { m_map = new Node[m_blocksize]; @@ -1195,9 +1141,6 @@ public class NodeSet */ public boolean removeElement(Node s) { - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - if (null == m_map) return false; @@ -1258,9 +1201,6 @@ public class NodeSet */ public void setElementAt(Node node, int index) { - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESET_NOT_MUTABLE, null)); //"This NodeSet is not mutable!"); - if (null == m_map) { m_map = new Node[m_blocksize]; diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java index 6b0c1bde1b5..ae0e196f0bb 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/NodeSetDTM.java @@ -1,6 +1,5 @@ /* - * reserved comment block - * DO NOT REMOVE OR ALTER! + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -57,6 +56,7 @@ import org.w3c.dom.traversal.NodeIterator; * to the same calls; the disadvantage is that some of them may return * less-than-enlightening results when you do so.

* @xsl.usage advanced + * @LastModified: May 2022 */ public class NodeSetDTM extends NodeVector implements /* NodeList, NodeIterator, */ DTMIterator, @@ -536,11 +536,7 @@ public class NodeSetDTM extends NodeVector */ public void addNode(int n) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - - this.addElement(n); + addElement(n); } /** @@ -554,10 +550,6 @@ public class NodeSetDTM extends NodeVector */ public void insertNode(int n, int pos) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - insertElementAt(n, pos); } @@ -570,10 +562,6 @@ public class NodeSetDTM extends NodeVector */ public void removeNode(int n) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - this.removeElement(n); } @@ -647,10 +635,6 @@ public class NodeSetDTM extends NodeVector */ public void addNodes(DTMIterator iterator) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - if (null != iterator) // defensive to fix a bug that Sanjiva reported. { int obj; @@ -704,10 +688,6 @@ public class NodeSetDTM extends NodeVector */ public void addNodesInDocOrder(DTMIterator iterator, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - int node; while (DTM.NULL != (node = iterator.nextNode())) @@ -793,10 +773,6 @@ public class NodeSetDTM extends NodeVector */ public int addNodeInDocOrder(int node, boolean test, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - int insertIndex = -1; if (test) @@ -868,10 +844,6 @@ public class NodeSetDTM extends NodeVector */ public int addNodeInDocOrder(int node, XPathContext support) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - return addNodeInDocOrder(node, true, support); } // end addNodeInDocOrder(Vector v, Object obj) @@ -894,10 +866,6 @@ public class NodeSetDTM extends NodeVector */ public void addElement(int value) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.addElement(value); } @@ -914,10 +882,6 @@ public class NodeSetDTM extends NodeVector */ public void insertElementAt(int value, int at) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.insertElementAt(value, at); } @@ -930,10 +894,6 @@ public class NodeSetDTM extends NodeVector */ public void appendNodes(NodeVector nodes) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.appendNodes(nodes); } @@ -947,10 +907,6 @@ public class NodeSetDTM extends NodeVector */ public void removeAllElements() { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.removeAllElements(); } @@ -969,10 +925,6 @@ public class NodeSetDTM extends NodeVector */ public boolean removeElement(int s) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - return super.removeElement(s); } @@ -988,10 +940,6 @@ public class NodeSetDTM extends NodeVector */ public void removeElementAt(int i) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.removeElementAt(i); } @@ -1009,10 +957,6 @@ public class NodeSetDTM extends NodeVector */ public void setElementAt(int node, int index) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.setElementAt(node, index); } @@ -1026,10 +970,6 @@ public class NodeSetDTM extends NodeVector */ public void setItem(int node, int index) { - - if (!m_mutable) - throw new RuntimeException(XSLMessages.createXPATHMessage(XPATHErrorResources.ER_NODESETDTM_NOT_MUTABLE, null)); //"This NodeSetDTM is not mutable!"); - super.setElementAt(node, index); } @@ -1157,9 +1097,6 @@ public class NodeSetDTM extends NodeVector return n; } - /** True if this list can be mutated. */ - transient protected boolean m_mutable = true; - /** True if this list is cached. * @serial */ transient protected boolean m_cacheNodes = true; @@ -1197,7 +1134,6 @@ public class NodeSetDTM extends NodeVector XSLMessages.createXPATHMessage(XPATHErrorResources.ER_CANNOT_CALL_SETSHOULDCACHENODE, null)); //"Can not call setShouldCacheNodes after nextNode has been called!"); m_cacheNodes = b; - m_mutable = true; } /** @@ -1208,7 +1144,7 @@ public class NodeSetDTM extends NodeVector */ public boolean isMutable() { - return m_mutable; + return true; } transient private int m_last = 0; diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java index 18632eab6ab..bd2d5a093a0 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/XPath.java @@ -42,7 +42,7 @@ import jdk.xml.internal.XMLSecurityManager; * The XPath class wraps an expression object and provides general services * for execution of that expression. * @xsl.usage advanced - * @LastModified: Jan 2022 + * @LastModified: May 2022 */ public class XPath implements Serializable, ExpressionOwner { @@ -208,22 +208,16 @@ public class XPath implements Serializable, ExpressionOwner else if (MATCH == type) parser.initMatchPattern(compiler, exprString, prefixResolver); else - throw new RuntimeException(XSLMessages.createXPATHMessage( + throw new TransformerException(XSLMessages.createXPATHMessage( XPATHErrorResources.ER_CANNOT_DEAL_XPATH_TYPE, new Object[]{Integer.toString(type)})); - //"Can not deal with XPath type: " + type); - // System.out.println("----------------"); - Expression expr = compiler.compileExpression(0); - - // System.out.println("expr: "+expr); - this.setExpression(expr); + m_mainExp = compiler.compileExpression(0); if((null != locator) && locator instanceof ExpressionNode) { - expr.exprSetParent((ExpressionNode)locator); + m_mainExp.exprSetParent((ExpressionNode)locator); } - } /** @@ -274,7 +268,7 @@ public class XPath implements Serializable, ExpressionOwner */ public XPath(Expression expr) { - this.setExpression(expr); + m_mainExp = expr; initFunctionTable(); } diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java index 6e52dc80393..b4ecbee15a0 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/axes/PredicatedNodeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -24,6 +24,7 @@ import com.sun.org.apache.xml.internal.dtm.DTM; import com.sun.org.apache.xml.internal.dtm.DTMIterator; import com.sun.org.apache.xml.internal.utils.PrefixResolver; import com.sun.org.apache.xml.internal.utils.QName; +import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException; import com.sun.org.apache.xpath.internal.Expression; import com.sun.org.apache.xpath.internal.ExpressionOwner; import com.sun.org.apache.xpath.internal.XPathContext; @@ -34,7 +35,7 @@ import com.sun.org.apache.xpath.internal.patterns.NodeTest; import java.util.List; /** - * @LastModified: May 2020 + * @LastModified: May 2022 */ public abstract class PredicatedNodeTest extends NodeTest implements SubContextList { @@ -491,9 +492,8 @@ public abstract class PredicatedNodeTest extends NodeTest implements SubContextL } catch (javax.xml.transform.TransformerException se) { - - // TODO: Fix this. - throw new RuntimeException(se.getMessage()); + // the Xalan/XPath impl use WrappedRuntimeException to carry errors over + throw new WrappedRuntimeException(se); } finally { diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java index 1efc5a32c37..e7f2f567c84 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/compiler/XPathParser.java @@ -35,7 +35,7 @@ import jdk.xml.internal.XMLSecurityManager; * Tokenizes and parses XPath expressions. This should really be named * XPathParserImpl, and may be renamed in the future. * @xsl.usage general - * @LastModified: Apr 2022 + * @LastModified: May 2022 */ public class XPathParser { @@ -79,6 +79,16 @@ public class XPathParser // XML security manager XMLSecurityManager m_xmlSecMgr; + // Union operands must be node-sets, e.g. //a | //b + // A flag indicating whether the operand is a location path + boolean isLocationPath = false; + + // A flag indicating whether the next operand is required to be a location path + boolean lPathRequired = false; + + // Keep track of the status of reading the next token after lPathRequired is flagged + boolean nextTokenRead = false; + /** * The parser constructor. */ @@ -434,9 +444,19 @@ public class XPathParser * Retrieve the next token from the command and * store it in m_token string. */ - private final void nextToken() + private final void nextToken() throws TransformerException { - + // before reading another token, check the last one + if (lPathRequired) { + if (nextTokenRead) { + // check whether the operand behind the union was a Location path + checkNodeSet(); + lPathRequired = false; + nextTokenRead = false; + } else { + nextTokenRead = true; + } + } if (m_queueMark < m_ops.getTokenQueueSize()) { m_token = (String) m_ops.m_tokenQueue.elementAt(m_queueMark++); @@ -497,31 +517,28 @@ public class XPathParser } /** - * Consume an expected token, throwing an exception if it - * isn't there. - * - * @param expected The string to be expected. - * - * @throws TransformerException + * Checks whether the function token represents a function that returns a + * nodeset. + * @param funcTok the function token + * @return true if the function token represents a function that returns a + * nodeset, false otherwise */ - private final void consumeExpected(String expected) - throws TransformerException - { + private boolean isNodesetFunction(int funcTok) { + return (funcTok == FunctionTable.FUNC_CURRENT || funcTok == FunctionTable.FUNC_HERE + || funcTok == FunctionTable.FUNC_ID); + } - if (tokenIs(expected)) + /** + * Checks whether the operand is a location path, reports error if not. + * + * @throws TransformerException if an error is found + */ + private void checkNodeSet() throws TransformerException { + if (!isLocationPath) { - nextToken(); + error(XPATHErrorResources.ER_UNION_MUST_BE_NODESET, + new Object[]{}); } - else - { - error(XPATHErrorResources.ER_EXPECTED_BUT_FOUND, new Object[]{ expected, - m_token }); //"Expected "+expected+", but found: "+m_token); - - // Patch for Christina's gripe. She wants her errorHandler to return from - // this error and continue trying to parse, rather than throwing an exception. - // Without the patch, that put us into an endless loop. - throw new XPathProcessorException(CONTINUE_AFTER_FATAL_ERROR); - } } /** @@ -1209,13 +1226,17 @@ public class XPathParser if (tokenIs(Token.VBAR)) { + // check whether the operand before the union is a location path + checkNodeSet(); + if (false == foundUnion) { foundUnion = true; - insertOp(opPos, 2, OpCodes.OP_UNION); } + isLocationPath = false; + lPathRequired = true; nextToken(); } else @@ -1464,7 +1485,7 @@ public class XPathParser m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, m_queueMark - 1); nextToken(); - consumeExpected(':'); + consumeExpected(Token.COLON); m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 2, m_queueMark - 1); @@ -1494,6 +1515,10 @@ public class XPathParser m_ops.setOp(opPos + OpMap.MAPINDEX_LENGTH + 1, funcTok); } + // XML Signature here() function returns a node-set + if (isNodesetFunction(funcTok)) { + isLocationPath = true; + } nextToken(); } @@ -1544,7 +1569,7 @@ public class XPathParser */ protected void LocationPath() throws TransformerException { - + isLocationPath = true; int opPos = m_ops.getOp(OpMap.MAPINDEX_LENGTH); // int locationPathOpPos = opPos; @@ -1861,7 +1886,7 @@ public class XPathParser } nextToken(); - consumeExpected(':'); + consumeExpected(Token.COLON); } else { @@ -1908,7 +1933,7 @@ public class XPathParser nextToken(); PredicateExpr(); countPredicate--; - consumeExpected(']'); + consumeExpected(Token.RBRACK); } } @@ -1950,7 +1975,7 @@ public class XPathParser m_ops.setOp(OpMap.MAPINDEX_LENGTH, m_ops.getOp(OpMap.MAPINDEX_LENGTH) + 1); nextToken(); - consumeExpected(':'); + consumeExpected(Token.COLON); } else { @@ -1969,7 +1994,7 @@ public class XPathParser * NCName ::= (Letter | '_') (NCNameChar) * NCNameChar ::= Letter | Digit | '.' | '-' | '_' | CombiningChar | Extender */ - protected void NCName() + protected void NCName() throws TransformerException { m_ops.setOp(m_ops.getOp(OpMap.MAPINDEX_LENGTH), m_queueMark - 1); diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java index ac1fd12745f..6fe57d28d2f 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/functions/FuncExtFunction.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2017, 2022, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -34,13 +34,14 @@ import com.sun.org.apache.xpath.internal.res.XPATHErrorResources; import com.sun.org.apache.xpath.internal.res.XPATHMessages; import java.util.ArrayList; import java.util.List; +import javax.xml.transform.TransformerException; /** * An object of this class represents an extension call expression. When * the expression executes, it calls ExtensionsTable#extFunction, and then * converts the result to the appropriate XObject. * @xsl.usage advanced - * @LastModified: Oct 2017 + * @LastModified: May 2022 */ public class FuncExtFunction extends Function { @@ -183,8 +184,7 @@ public class FuncExtFunction extends Function * * @throws javax.xml.transform.TransformerException */ - public XObject execute(XPathContext xctxt) - throws javax.xml.transform.TransformerException + public XObject execute(XPathContext xctxt) throws TransformerException { if (xctxt.isSecureProcessing()) throw new javax.xml.transform.TransformerException( @@ -209,6 +209,12 @@ public class FuncExtFunction extends Function } //dml ExtensionsProvider extProvider = (ExtensionsProvider)xctxt.getOwnerObject(); + if (extProvider == null) { + String fmsg = XSLMessages.createXPATHMessage( + XPATHErrorResources.ER_NO_XPATH_FUNCTION_PROVIDER, + new Object[] {argVec} ); + throw new TransformerException ( fmsg ); + } Object val = extProvider.extFunction(this, argVec); if (null != val) diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java index 46e7996fd9a..4a75ae4f1ff 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/JAXPVariableStack.java @@ -1,6 +1,5 @@ /* - * reserved comment block - * DO NOT REMOVE OR ALTER! + * Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved. */ /* * Licensed to the Apache Software Foundation (ASF) under one or more @@ -38,6 +37,7 @@ import com.sun.org.apache.xalan.internal.res.XSLMessages; * {@link javax.xml.xpath.XPathVariableResolver}. * * @author Ramesh Mandava + * @LastModified: May 2022 */ public class JAXPVariableStack extends VariableStack { @@ -61,6 +61,13 @@ public class JAXPVariableStack extends VariableStack { new javax.xml.namespace.QName( qname.getNamespace(), qname.getLocalPart()); + + if (resolver == null) { + String fmsg = XSLMessages.createXPATHMessage( + XPATHErrorResources.ER_NO_XPATH_VARIABLE_RESOLVER, + new Object[] { name.toString()} ); + throw new TransformerException( fmsg ); + } Object varValue = resolver.resolveVariable( name ); if ( varValue == null ) { String fmsg = XSLMessages.createXPATHMessage( diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java index 79f6dc382bb..5ded2b99eaa 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathExpressionImpl.java @@ -20,6 +20,7 @@ package com.sun.org.apache.xpath.internal.jaxp; +import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException; import com.sun.org.apache.xpath.internal.objects.XObject; import javax.xml.namespace.QName; import javax.xml.transform.TransformerException; @@ -27,6 +28,7 @@ import javax.xml.xpath.XPathConstants; import javax.xml.xpath.XPathEvaluationResult; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; +import javax.xml.xpath.XPathFunctionException; import javax.xml.xpath.XPathFunctionResolver; import javax.xml.xpath.XPathVariableResolver; import jdk.xml.internal.JdkXmlFeatures; @@ -37,7 +39,7 @@ import org.xml.sax.InputSource; * The XPathExpression interface encapsulates a (compiled) XPath expression. * * @author Ramesh Mandava - * @LastModified: Apr 2022 + * @LastModified: May 2022 */ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpression { @@ -77,7 +79,7 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio } public Object eval(Object item, QName returnType) - throws javax.xml.transform.TransformerException { + throws TransformerException { XObject resultObject = eval(item, xpath); return getResultAsType(resultObject, returnType); } @@ -88,20 +90,20 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio isSupported(returnType); try { return eval(item, returnType); - } catch (java.lang.NullPointerException npe) { - // If VariableResolver returns null Or if we get - // NullPointerException at this stage for some other reason - // then we have to reurn XPathException - throw new XPathExpressionException (npe); - } catch (javax.xml.transform.TransformerException te) { + } catch (TransformerException te) { Throwable nestedException = te.getException(); - if (nestedException instanceof javax.xml.xpath.XPathFunctionException) { - throw (javax.xml.xpath.XPathFunctionException)nestedException; + if (nestedException instanceof XPathFunctionException) { + throw (XPathFunctionException)nestedException; } else { // For any other exceptions we need to throw // XPathExpressionException (as per spec) throw new XPathExpressionException(te); } + } catch (RuntimeException re) { + if (re instanceof WrappedRuntimeException) { + throw new XPathExpressionException(((WrappedRuntimeException)re).getException()); + } + throw new XPathExpressionException(re); } } @@ -115,12 +117,18 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio @Override public Object evaluate(InputSource source, QName returnType) throws XPathExpressionException { + requireNonNull(source, "Source"); isSupported (returnType); try { Document document = getDocument(source); return eval(document, returnType); } catch (TransformerException e) { throw new XPathExpressionException(e); + } catch (RuntimeException re) { + if (re instanceof WrappedRuntimeException) { + throw new XPathExpressionException(((WrappedRuntimeException)re).getException()); + } + throw new XPathExpressionException(re); } } @@ -143,8 +151,13 @@ public class XPathExpressionImpl extends XPathImplUtil implements XPathExpressio return XPathResultImpl.getValue(resultObject, type); } - } catch (javax.xml.transform.TransformerException te) { + } catch (TransformerException te) { throw new XPathExpressionException(te); + } catch (RuntimeException re) { + if (re instanceof WrappedRuntimeException) { + throw new XPathExpressionException(((WrappedRuntimeException)re).getException()); + } + throw new XPathExpressionException(re); } } diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java index c070b2806f2..79a06eec785 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/jaxp/XPathImpl.java @@ -20,6 +20,7 @@ package com.sun.org.apache.xpath.internal.jaxp; +import com.sun.org.apache.xml.internal.utils.WrappedRuntimeException; import com.sun.org.apache.xpath.internal.*; import com.sun.org.apache.xpath.internal.objects.XObject; import javax.xml.namespace.NamespaceContext; @@ -47,7 +48,7 @@ import org.xml.sax.InputSource; * New methods: evaluateExpression * Refactored to share code with XPathExpressionImpl. * - * @LastModified: Jan 2022 + * @LastModified: May 2022 */ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath { @@ -134,11 +135,6 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath { XObject resultObject = eval(expression, item); return getResultAsType(resultObject, returnType); - } catch (java.lang.NullPointerException npe) { - // If VariableResolver returns null Or if we get - // NullPointerException at this stage for some other reason - // then we have to reurn XPathException - throw new XPathExpressionException (npe); } catch (TransformerException te) { Throwable nestedException = te.getException(); if (nestedException instanceof javax.xml.xpath.XPathFunctionException) { @@ -146,10 +142,14 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath { } else { // For any other exceptions we need to throw // XPathExpressionException (as per spec) - throw new XPathExpressionException (te); + throw new XPathExpressionException(te); } + } catch (RuntimeException re) { + if (re instanceof WrappedRuntimeException) { + throw new XPathExpressionException(((WrappedRuntimeException)re).getException()); + } + throw new XPathExpressionException(re); } - } //-Override- @@ -172,26 +172,18 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath { return ximpl; } catch (TransformerException te) { throw new XPathExpressionException (te) ; + } catch (RuntimeException re) { + if (re instanceof WrappedRuntimeException) { + throw new XPathExpressionException(((WrappedRuntimeException)re).getException()); + } + throw new XPathExpressionException(re); } } //-Override- public Object evaluate(String expression, InputSource source, QName returnType) throws XPathExpressionException { - isSupported(returnType); - - try { - Document document = getDocument(source); - XObject resultObject = eval(expression, document); - return getResultAsType(resultObject, returnType); - } catch (TransformerException te) { - Throwable nestedException = te.getException(); - if (nestedException instanceof javax.xml.xpath.XPathFunctionException) { - throw (javax.xml.xpath.XPathFunctionException)nestedException; - } else { - throw new XPathExpressionException (te); - } - } + return evaluate(expression, getDocument(source), returnType); } //-Override- @@ -210,7 +202,8 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath { //-Override- public T evaluateExpression(String expression, Object item, Class type) throws XPathExpressionException { - isSupportedClassType(type); + requireNonNull(expression, "XPath expression"); + isSupportedClassType(type); try { XObject resultObject = eval(expression, item); if (type == XPathEvaluationResult.class) { @@ -219,7 +212,12 @@ public class XPathImpl extends XPathImplUtil implements javax.xml.xpath.XPath { return XPathResultImpl.getValue(resultObject, type); } } catch (TransformerException te) { - throw new XPathExpressionException (te); + throw new XPathExpressionException(te); + } catch (RuntimeException re) { + if (re instanceof WrappedRuntimeException) { + throw new XPathExpressionException(((WrappedRuntimeException)re).getException()); + } + throw new XPathExpressionException(re); } } diff --git a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java index 9df76ef6632..409d06e638e 100644 --- a/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java +++ b/src/java.xml/share/classes/com/sun/org/apache/xpath/internal/res/XPATHErrorResources.java @@ -31,7 +31,7 @@ import java.util.ListResourceBundle; * Also you need to update the count of messages(MAX_CODE)or * the count of warnings(MAX_WARNING) [ Information purpose only] * @xsl.usage advanced - * @LastModified: Apr 2022 + * @LastModified: May 2022 */ public class XPATHErrorResources extends ListResourceBundle { @@ -140,6 +140,7 @@ public class XPATHErrorResources extends ListResourceBundle "ER_EXPECTED_SINGLE_QUOTE"; public static final String ER_EMPTY_EXPRESSION = "ER_EMPTY_EXPRESSION"; public static final String ER_EXPECTED_BUT_FOUND = "ER_EXPECTED_BUT_FOUND"; + public static final String ER_UNION_MUST_BE_NODESET = "ER_UNION_MUST_BE_NODESET"; public static final String ER_INCORRECT_PROGRAMMER_ASSERTION = "ER_INCORRECT_PROGRAMMER_ASSERTION"; public static final String ER_BOOLEAN_ARG_NO_LONGER_OPTIONAL = @@ -203,9 +204,6 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = "ER_FUNCTION_TOKEN_NOT_FOUND"; public static final String ER_CANNOT_DEAL_XPATH_TYPE = "ER_CANNOT_DEAL_XPATH_TYPE"; - public static final String ER_NODESET_NOT_MUTABLE = "ER_NODESET_NOT_MUTABLE"; - public static final String ER_NODESETDTM_NOT_MUTABLE = - "ER_NODESETDTM_NOT_MUTABLE"; /** Variable not resolvable: */ public static final String ER_VAR_NOT_RESOLVABLE = "ER_VAR_NOT_RESOLVABLE"; /** Null error handler */ @@ -309,6 +307,8 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = //BEGIN: Keys needed for exception messages of JAXP 1.3 XPath API implementation public static final String ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED = "ER_EXTENSION_FUNCTION_CANNOT_BE_INVOKED"; public static final String ER_RESOLVE_VARIABLE_RETURNS_NULL = "ER_RESOLVE_VARIABLE_RETURNS_NULL"; + public static final String ER_NO_XPATH_VARIABLE_RESOLVER = "ER_NO_XPATH_VARIABLE_RESOLVER"; + public static final String ER_NO_XPATH_FUNCTION_PROVIDER = "ER_NO_XPATH_FUNCTION_PROVIDER"; public static final String ER_UNSUPPORTED_RETURN_TYPE = "ER_UNSUPPORTED_RETURN_TYPE"; public static final String ER_SOURCE_RETURN_TYPE_CANNOT_BE_NULL = "ER_SOURCE_RETURN_TYPE_CANNOT_BE_NULL"; public static final String ER_ARG_CANNOT_BE_NULL = "ER_ARG_CANNOT_BE_NULL"; @@ -460,6 +460,9 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = { ER_EXPECTED_BUT_FOUND, "Expected {0}, but found: {1}"}, + { ER_UNION_MUST_BE_NODESET, + "Operands for a union must be node-sets."}, + { ER_INCORRECT_PROGRAMMER_ASSERTION, "Programmer assertion is incorrect! - {0}"}, @@ -574,12 +577,6 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = { ER_CANNOT_DEAL_XPATH_TYPE, "Can not deal with XPath type: {0}"}, - { ER_NODESET_NOT_MUTABLE, - "This NodeSet is not mutable"}, - - { ER_NODESETDTM_NOT_MUTABLE, - "This NodeSetDTM is not mutable"}, - { ER_VAR_NOT_RESOLVABLE, "Variable not resolvable: {0}"}, @@ -780,6 +777,12 @@ public static final String ER_IGNORABLE_WHITESPACE_NOT_HANDLED = { ER_RESOLVE_VARIABLE_RETURNS_NULL, "resolveVariable for variable {0} returning null"}, + { ER_NO_XPATH_VARIABLE_RESOLVER, + "Attempting to resolve variable {0}, but a variable resolver is not set."}, + + { ER_NO_XPATH_FUNCTION_PROVIDER, + "Attempting to call an extension function {0}, but an extension provider is not set."}, + /** Field ER_UNSUPPORTED_RETURN_TYPE */ { ER_UNSUPPORTED_RETURN_TYPE, diff --git a/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java index 3b6a61745db..9069f0ad4ac 100644 --- a/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java +++ b/test/jaxp/javax/xml/jaxp/unittest/xpath/XPathExceptionTest.java @@ -24,34 +24,96 @@ package xpath; import java.io.StringReader; +import javax.xml.namespace.QName; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; +import javax.xml.parsers.ParserConfigurationException; import javax.xml.xpath.XPath; +import static javax.xml.xpath.XPathConstants.BOOLEAN; +import static javax.xml.xpath.XPathConstants.NODE; +import static javax.xml.xpath.XPathConstants.NODESET; +import static javax.xml.xpath.XPathConstants.NUMBER; +import static javax.xml.xpath.XPathConstants.STRING; import javax.xml.xpath.XPathExpression; import javax.xml.xpath.XPathExpressionException; import javax.xml.xpath.XPathFactory; +import javax.xml.xpath.XPathNodes; import org.testng.Assert; import org.testng.annotations.DataProvider; import org.testng.annotations.Test; import org.w3c.dom.Document; import org.w3c.dom.Node; +import org.xml.sax.InputSource; /* * @test - * @bug 8284548 + * @bug 8284400 8284548 * @run testng xpath.XPathExceptionTest - * @summary This is a general test for Exception handling. Additional cases may - * be added with a bug id in the test cases. + * @summary This is a general test for Exception handling in XPath. This test + * covers the followings: + * NPE: refer to DataProvider NPE for more details. + * IAE: covered by existing tests: Bug4991939, XPathAnyTypeTest, XPathExpAnyTypeTest + * and XPathTest + * XPathExpressionException: all other cass other than NPE and IAE. */ public class XPathExceptionTest { + private final String XPATH_EXPRESSION = "ext:helloWorld()"; /* - * DataProvider: invalid XPath expressions - * Illegal expressions and structures that may escape the validation check. + * DataProvider: used for NPE test, provides the following fields: + * expression, context, useSource, source, QName, class type, provided + * Refer to testNPEWithEvaluate. */ - @DataProvider(name = "invalid") - public Object[][] getInvalid() throws Exception { + @DataProvider(name = "NPE") + public Object[][] getNullParameter() throws Exception { return new Object[][]{ + /** + * Existing NPE tests: + * for XPath::evaluate: + * Bug4992788: expression != null, source = null + * Bug4992793: expression = null, source != null + * Bug4992805: source != null, QName = null + * + * for XPath::evaluateExpression + * XPathAnyTypeTest: expression = null or classType = null + */ + // NPE if expression = null + {null, (Node)null, false, null, STRING, String.class, true}, + {null, getDummyDoc(), false, null, STRING, String.class, true}, + {null, (Node)null, false, null, null, null, false}, + {null, getDummyDoc(), false, null, null, null, false}, + // NPE if returnType = null + {"exp", (Node)null, false, null, null, null, true}, + {"exp", getDummyDoc(), false, null, null, null, true}, + // NPE if source = null + {"exp", (Node)null, true, null, STRING, String.class, true}, + {"exp", getDummyDoc(), true, null, STRING, String.class, true}, + {"exp", (Node)null, true, null, null, null, false}, + {"exp", getDummyDoc(), true, null, null, null, false}, + }; + } + + /* + * DataProvider: used for compile-time error test, provides: + * invalid XPath expressions + */ + @DataProvider(name = "invalidExp") + public Object[][] getInvalidExp() throws Exception { + return new Object[][]{ + {"8|b"}, + {"8[x=2]|b"}, + {"8/a|b"}, + {"a|7"}, + {"a|7|b"}, + {"a|7[x=2]"}, + {"b|'literal'"}, + {"b|\"literal\""}, + {"a|$x:y"}, + {"a|(x or y)"}, + {"a|(x and y)"}, + {"a|(x=2)"}, + {"a|string-length(\"xy\")"}, + {"/a/b/preceding-sibling::comment()|7"}, // @bug JDK-8284548: expressions ending with relational operators // throw StringIndexOutOfBoundsException instead of XPathExpressionException {"/a/b/c[@d >"}, @@ -61,22 +123,476 @@ public class XPathExceptionTest { }; } + /* + * DataProvider: expressions that cause exceptions in the given context. + */ + @DataProvider(name = "expInContext1") + public Object[][] getExpressionAndContext1() throws Exception { + InputSource source = new InputSource(new StringReader("")); + return new Object[][]{ + + // expressions invalid for the null context, return type not provided + {"x+1", (Node)null, false, null, null, null, false}, + {"5 mod a", (Node)null, false, null, null, null, false}, + {"8 div ", (Node)null, false, null, null, null, false}, + {"/bookstore/book[price>xx]", (Node)null, false, null, null, null, false}, + // expressions invalid for the null context, return type is irrelevant + // for the eval, but needs to be a valid one when used + // Note that invalid class type was tested in XPathAnyTypeTest, + // and invalid QName tested in Bug4991939. + {"x+1", (Node)null, false, null, STRING, String.class, true}, + {"5 mod a", (Node)null, false, null, STRING, String.class, true}, + {"8 div ", (Node)null, false, null, STRING, String.class, true}, + {"/bookstore/book[price>xx]", (Node)null, false, null, STRING, String.class, true}, + + // undefined variable, context not relevant, return type not provided + {"/ * [n*$d2]/s", getDummyDoc(), false, null, null, null, false}, + {"/ * [n|$d1]/s", getDummyDoc(), false, null, null, null, false}, + {"/ * [n*$d2]/s", null, true, source, null, null, false}, + {"/ * [n|$d1]/s", null, true, source, null, null, false}, + // undefined variable, context/return type not relevant for the eval + // but need to be valid when provided + {"/ * [n*$d2]/s", getDummyDoc(), false, null, STRING, String.class, true}, + {"/ * [n|$d1]/s", getDummyDoc(), false, null, STRING, String.class, true}, + {"/ * [n*$d2]/s", null, true, source, STRING, String.class, true}, + {"/ * [n|$d1]/s", null, true, source, STRING, String.class, true}, + }; + } + + /* + * DataProvider: provides edge cases that are valid + */ + @DataProvider(name = "expInContext2") + public Object[][] getExpressionAndContext2() throws Exception { + return new Object[][]{ + // The context can be empty + {"/node[x=2]", getEmptyDocument(), false, null, STRING, String.class, true}, + {"/a/b/c", getEmptyDocument(), false, null, BOOLEAN, Boolean.class, true}, + }; + } + + /* + * DataProvider: provides expressions that contain function calls. + */ + @DataProvider(name = "functions") + public Object[][] getExpressionWithFunctions() throws Exception { + InputSource source = new InputSource(new StringReader("")); + return new Object[][]{ + // expression with a function call + {XPATH_EXPRESSION, getEmptyDocument(), false, null, STRING, String.class, true}, + {XPATH_EXPRESSION, null, true, source, BOOLEAN, Boolean.class, true}, + }; + } + /** - * Verifies that the XPath processor throws XPathExpressionException upon - * encountering illegal XPath expressions. + * Verifies that NPE is thrown if the expression, source, or returnType is + * null. + * This test tests these methods: + * XPath::evaluate and XPathExpression::evaluate. + * XPath::evaluateExpression and XPathExpression::evaluateExpression. + * + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param cls the return type in the form of a class type, can be null + * @param provided a flag indicating whether the return type is provided + * @throws Exception if the test fails + */ + @Test(dataProvider = "NPE") + public void testNPEWithEvaluate(String expression, Object item, boolean useSource, + InputSource source, QName qn, Class cls, boolean provided) + throws Exception { + XPath xPath = XPathFactory.newInstance().newXPath(); + + // test with XPath::evaluate + Assert.assertThrows(NullPointerException.class, () -> xpathEvaluate( + xPath, expression, item, useSource, source, qn, provided)); + + // test with XPathExpression::evaluate + Assert.assertThrows(NullPointerException.class, () -> xpathExpressionEvaluate( + xPath, expression, item, useSource, source, qn, provided)); + + // test with XPath::evaluateExpression + Assert.assertThrows(NullPointerException.class, () -> xpathEvaluateExpression( + xPath, expression, item, useSource, source, cls, provided)); + + // test with XPathExpression::evaluateExpression + Assert.assertThrows(NullPointerException.class, () -> xpathExpressionEvaluateExpression( + xPath, expression, item, useSource, source, cls, provided)); + } + + /** + * Verifies that XPathExpressionException is thrown upon encountering illegal + * XPath expressions when XPath::compile is called. + * * @param invalidExp an illegal XPath expression * @throws Exception if the test fails */ - @Test(dataProvider = "invalid") - public void testIllegalExp(String invalidExp) throws Exception { - DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); - Document doc = builder.parse(new org.xml.sax.InputSource(new StringReader(""))); - Assert.assertThrows(XPathExpressionException.class, () -> evaluate(doc, invalidExp)); + @Test(dataProvider = "invalidExp") + public void testXPathCompile(String invalidExp) throws Exception { + Assert.assertThrows(XPathExpressionException.class, () -> xpathCompile(invalidExp)); } - private void evaluate(Document doc, String s) throws XPathExpressionException { + /** + * Verifies that XPathExpressionException is thrown upon encountering illegal + * XPath expressions. + * This test tests these methods: + * XPath::evaluate and XPathExpression::evaluate. + * XPath::evaluateExpression and XPathExpression::evaluateExpression. + + * + * @param expression an illegal XPath expression + * @throws Exception if the test fails + */ + @Test(dataProvider = "invalidExp") + public void testCompileErrorWithEvaluate(String expression) + throws Exception { + XPath xPath = XPathFactory.newInstance().newXPath(); + + // test with XPath::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate( + xPath, expression, (Node)null, false, null, null, false)); + + // test with XPathExpression::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate( + xPath, expression, (Node)null, false, null, null, false)); + + // test with XPath::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression( + xPath, expression, (Node)null, false, null, null, false)); + + // test with XPathExpression::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression( + xPath, expression, (Node)null, false, null, null, false)); + } + + /** + * Verifies that XPathExpressionException is thrown if the expression is + * invalid with the given context. + * This test tests these methods: + * XPath::evaluate and XPathExpression::evaluate. + * XPath::evaluateExpression and XPathExpression::evaluateExpression. + * + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param cls the return type in the form of a class type, can be null + * @param provided a flag indicating whether the return type is provided + * @throws Exception if the test fails + */ + @Test(dataProvider = "expInContext1") + public void testExpInContextEval1(String expression, Object item, boolean useSource, + InputSource source, QName qn, Class cls, boolean provided) + throws Exception { + XPath xPath = XPathFactory.newInstance().newXPath(); + + // test with XPath::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate( + xPath, expression, item, useSource, source, qn, provided)); + + // test with XPathExpression::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate( + xPath, expression, item, useSource, source, qn, provided)); + + // test with XPath::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression( + xPath, expression, item, useSource, source, cls, provided)); + + // test with XPathExpression::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression( + xPath, expression, item, useSource, source, cls, provided)); + } + + /** + * Verifies that XPathExpressionException is thrown if the expression is + * invalid with the given context. + * This test tests these methods: + * XPath::evaluate and XPathExpression::evaluate. + * XPath::evaluateExpression and XPathExpression::evaluateExpression. + * + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param cls the return type in the form of a class type, can be null + * @param provided a flag indicating whether the return type is provided + * @throws Exception if the test fails + */ + @Test(dataProvider = "expInContext1") + public void testExpInContext(String expression, Object item, boolean useSource, + InputSource source, QName qn, Class cls, boolean provided) + throws Exception { + QName[] qns = {NUMBER, STRING, BOOLEAN, NODESET, NODE}; + Class[] classes = {Integer.class, Boolean.class, String.class, XPathNodes.class, Node.class}; + XPath xPath = XPathFactory.newInstance().newXPath(); + + for (QName qn1 : qns) { + // test with XPath::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate( + xPath, expression, item, useSource, source, qn1, provided)); + + // test with XPathExpression::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate( + xPath, expression, item, useSource, source, qn1, provided)); + } + + for (Class cls1 : classes) { + // test with XPath::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression( + xPath, expression, item, useSource, source, cls1, provided)); + + // test with XPathExpression::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression( + xPath, expression, item, useSource, source, cls1, provided)); + } + } + + /** + * Verifies that the expression is valid with the given context. + * This test tests these methods: + * XPath::evaluate and XPathExpression::evaluate. + * XPath::evaluateExpression and XPathExpression::evaluateExpression. + * + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param cls the return type in the form of a class type, can be null + * @param provided a flag indicating whether the return type is provided + * @throws Exception if the test fails + */ + @Test(dataProvider = "expInContext2") + public void testExpInContextEval2(String expression, Object item, boolean useSource, + InputSource source, QName qn, Class cls, boolean provided) + throws Exception { + XPath xPath = XPathFactory.newInstance().newXPath(); + + // test with XPath::evaluate + xpathEvaluate(xPath, expression, item, useSource, source, qn, provided); + + // test with XPathExpression::evaluate + xpathExpressionEvaluate(xPath, expression, item, useSource, source, qn, provided); + + // test with XPath::evaluateExpression + xpathEvaluateExpression(xPath, expression, item, useSource, source, cls, provided); + + // test with XPathExpression::evaluateExpression + xpathExpressionEvaluateExpression(xPath, expression, item, useSource, source, cls, provided); + } + + /** + * Verifies that the XPath processor without XPathFunctionResolver throws + * XPathExpressionException upon processing an XPath expression that attempts + * to call a function. + * + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param cls the return type in the form of a class type, can be null + * @param provided a flag indicating whether the return type is provided + * @throws Exception if the test fails + */ + @Test(dataProvider = "functions") + public void testFunction(String expression, Object item, boolean useSource, + InputSource source, QName qn, Class cls, boolean provided) + throws Exception { + XPath xPath = XPathFactory.newInstance().newXPath(); + + // test with XPath::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluate( + xPath, expression, item, useSource, source, qn, provided)); + + // test with XPathExpression::evaluate + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluate( + xPath, expression, item, useSource, source, qn, provided)); + + // test with XPath::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathEvaluateExpression( + xPath, expression, item, useSource, source, cls, provided)); + + // test with XPathExpression::evaluateExpression + Assert.assertThrows(XPathExpressionException.class, () -> xpathExpressionEvaluateExpression( + xPath, expression, item, useSource, source, cls, provided)); + } + +// ---- utility methods ---- + /** + * Compiles the specified expression. + * @param s the expression + * @throws XPathExpressionException if the expression is invalid + */ + private void xpathCompile(String s) throws XPathExpressionException { XPath xp = XPathFactory.newInstance().newXPath(); XPathExpression xe = xp.compile(s); - xe.evaluateExpression(doc, Node.class); + } + + /** + * Runs evaluation using the XPath::evaluate methods. + * + * @param xPath the XPath object + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param qnProvided a flag indicating whether the QName is provided + * @throws XPathExpressionException + */ + private void xpathEvaluate(XPath xPath, String expression, Object item, + boolean useSource, InputSource source, QName qn, boolean qnProvided) + throws XPathExpressionException { + if (useSource) { + if (!qnProvided) { + xPath.evaluate(expression, source); + } else { + xPath.evaluate(expression, source, qn); + } + } else { + if (!qnProvided) { + xPath.evaluate(expression, item); + } else { + xPath.evaluate(expression, item, qn); + } + } + } + + /** + * Runs evaluation using the XPathExpression::evaluate methods. + * + * @param xe the XPathExpression object + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param qnProvided a flag indicating whether the QName is provided + * @throws XPathExpressionException + */ + private void xpathExpressionEvaluate(XPath xPath, String expression, Object item, + boolean useSource, InputSource source, QName qn, boolean qnProvided) + throws XPathExpressionException { + XPathExpression xe = xPath.compile(expression); + if (useSource) { + if (!qnProvided) { + xe.evaluate(source); + } else { + xe.evaluate(source, qn); + } + } else { + if (!qnProvided) { + xe.evaluate(item); + } else { + xe.evaluate(item, qn); + } + } + } + + /** + * Runs evaluation using the XPath::evaluateExpression methods. + * + * @param xPath the XPath object + * @param expression the expression + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param cls the class type, can be null + * @param clsProvided a flag indicating whether the class type is provided + * @throws XPathExpressionException + */ + private void xpathEvaluateExpression(XPath xPath, String expression, Object item, + boolean useSource, InputSource source, Class cls, boolean clsProvided) + throws XPathExpressionException { + if (useSource) { + if (!clsProvided) { + xPath.evaluateExpression(expression, source); + } else { + xPath.evaluateExpression(expression, source, cls); + } + } else { + if (!clsProvided) { + xPath.evaluateExpression(expression, item); + } else { + xPath.evaluateExpression(expression, item, cls); + } + } + } + + /** + * Runs evaluation using the XPathExpression::evaluateExpression methods. + * + * @param xe the XPathExpression object + * @param item the context item, can be null (for non-context dependent expressions) + * @param useSource a flag indicating whether the source shall be used instead + * of the context item + * @param source the source + * @param qn the return type in the form of a QName, can be null + * @param qnProvided a flag indicating whether the QName is provided + * @throws XPathExpressionException + */ + private void xpathExpressionEvaluateExpression(XPath xPath, String expression, + Object item, boolean useSource, InputSource source, Class cls, boolean qnProvided) + throws XPathExpressionException { + XPathExpression xe = xPath.compile(expression); + if (useSource) { + if (!qnProvided) { + xe.evaluateExpression(source); + } else { + xe.evaluateExpression(source, cls); + } + } else { + if (!qnProvided) { + xe.evaluateExpression(item); + } else { + xe.evaluateExpression(item, cls); + } + } + } + + /** + * Returns an empty {@link org.w3c.dom.Document}. + * @return a DOM Document, null in case of Exception + */ + private Document getEmptyDocument() { + try { + return DocumentBuilderFactory.newInstance().newDocumentBuilder().newDocument(); + } catch (ParserConfigurationException e) { + return null; + } + } + + /** + * Returns a DOM Document with dummy content. + * @return a DOM Document + * @throws Exception + */ + private Document getDummyDoc() throws Exception { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return builder.parse(new InputSource(new StringReader(""))); + } + + /** + * Returns a DOM Document with the specified source. + * @param s the source + * @return a DOM Document + * @throws Exception + */ + private Document getDoc(InputSource s) throws Exception { + DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder(); + return builder.parse(s); } }