From 27e2558fcf5d2c66e5f19bac7c57758f0febdde2 Mon Sep 17 00:00:00 2001 From: Akash Kumar <116457960+akashchamp@users.noreply.github.com> Date: Thu, 24 Sep 2026 23:52:15 +0530 Subject: [PATCH] [JXPATH-204] Eagerly evaluate UnionContext before returning its value UnionContext only populates its NodeSet lazily, inside setPosition(). A caller that reads getValue() without first iterating the context (for example, an extension function invoked with a union expression, e.g. test:fn(a | b), as one of its arguments) received an empty NodeSet instead of the union of the operand node-sets. Override getValue() to force the node set to be built via getContextNodeList() before delegating to the parent implementation, per the fix suggested in the JIRA report. Adds a regression test in ExtensionFunctionTest that invokes a custom NodeSet-consuming function with a union of two location paths and asserts the full pointer count is seen. Generated-by: Claude Sonnet 5 (Claude Code) --- .../apache/commons/jxpath/ri/axes/UnionContext.java | 11 +++++++++++ .../jxpath/ri/compiler/ExtensionFunctionTest.java | 11 ++++++++++- 2 files changed, 21 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/apache/commons/jxpath/ri/axes/UnionContext.java b/src/main/java/org/apache/commons/jxpath/ri/axes/UnionContext.java index 01a371b61..ff7473aa7 100644 --- a/src/main/java/org/apache/commons/jxpath/ri/axes/UnionContext.java +++ b/src/main/java/org/apache/commons/jxpath/ri/axes/UnionContext.java @@ -48,6 +48,17 @@ public int getDocumentOrder() { return contexts.length > 1 ? 1 : super.getDocumentOrder(); } + @Override + public Object getValue() { + // JXPATH-204: the node set is built lazily inside setPosition(), so + // force it to be populated before delegating, otherwise callers that + // read the value without first iterating (e.g. an extension + // function receiving this context as an argument) see an empty + // NodeSet. + getContextNodeList(); + return super.getValue(); + } + @Override public boolean setPosition(final int position) { if (!prepared) { diff --git a/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java b/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java index eb76e767b..3cf2adec3 100644 --- a/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java +++ b/src/test/java/org/apache/commons/jxpath/ri/compiler/ExtensionFunctionTest.java @@ -283,4 +283,13 @@ void testStaticMethodLookupWithExpressionContext() { final Function func = functions.getFunction("test", "path", args); assertEquals("1", func.invoke(new Context(Integer.valueOf(1)), args), "test:path()"); } -} \ No newline at end of file + + @Test + void testUnionOperatorArgument() { + // JXPATH-204: UnionContext computes its node list lazily (only inside + // setPosition()), so a custom function invoked with a union ('|') + // expression as an argument used to see an empty NodeSet instead of + // the union's actual nodes. + assertXPathValue(context, "test:countPointers(/beans[1] | /beans[2])", Integer.valueOf(2)); + } +}