11/*
2+ * Elemental
3+ * Copyright (C) 2024, Evolved Binary Ltd
4+ *
5+ * admin@evolvedbinary.com
6+ * https://www.evolvedbinary.com | https://www.elemental.xyz
7+ *
8+ * This library is free software; you can redistribute it and/or
9+ * modify it under the terms of the GNU Lesser General Public
10+ * License as published by the Free Software Foundation; version 2.1.
11+ *
12+ * This library is distributed in the hope that it will be useful,
13+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
14+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
15+ * Lesser General Public License for more details.
16+ *
17+ * You should have received a copy of the GNU Lesser General Public
18+ * License along with this library; if not, write to the Free Software
19+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
20+ *
21+ * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
22+ * The original license header is included below.
23+ *
24+ * =====================================================================
25+ *
226 * eXist-db Open Source Native XML Database
327 * Copyright (C) 2001 The eXist-db Authors
428 *
2953import org .exist .xquery .value .Sequence ;
3054import org .exist .xquery .value .Type ;
3155
56+ import javax .annotation .Nullable ;
3257import java .util .ArrayList ;
3358import java .util .List ;
3459
3560/**
3661 * Implements the XQuery 3.1 arrow operator.
3762 *
3863 * @author wolf
64+ * @author <a href="mailto:adam@evolvedbinary.com">Adam Retter</a>
3965 */
4066public class ArrowOperator extends AbstractExpression {
4167
42- private QName qname = null ;
68+ private @ Nullable QName functionName = null ;
4369 private Expression leftExpr ;
44- private FunctionCall fcall = null ;
45- private Expression funcSpec = null ;
46- private List <Expression > parameters ;
70+ @ Nullable FunctionCall functionCall = null ;
71+ private @ Nullable Expression functionSpecExpr = null ;
72+ private List <Expression > functionParameters ;
4773 private AnalyzeContextInfo cachedContextInfo ;
4874
4975 public ArrowOperator (final XQueryContext context , final Expression leftExpr ) throws
@@ -52,38 +78,53 @@ public ArrowOperator(final XQueryContext context, final Expression leftExpr) thr
5278 this .leftExpr = leftExpr ;
5379 }
5480
55- public void setArrowFunction (final String fname , final List <Expression > params ) throws XPathException {
81+ /**
82+ * Set the XPath/XQuery function to be called by name.
83+ *
84+ * @param functionName the fully qualified name of the function to call.
85+ * @param functionParameters the parameters for the function.
86+ * @throws XPathException if the fully qualified name is invalid.
87+ */
88+ public void setArrowFunction (final String functionName , final List <Expression > functionParameters ) throws XPathException {
5689 try {
57- this .qname = QName .parse (context , fname , context .getDefaultFunctionNamespace ());
58- this .parameters = params ;
90+ this .functionName = QName .parse (context , functionName , context .getDefaultFunctionNamespace ());
91+ this .functionParameters = functionParameters ;
5992 // defer resolving the function to analyze to make sure all functions are known
6093 } catch (final IllegalQNameException e ) {
61- throw new XPathException (this , ErrorCodes .XPST0081 , "No namespace defined for prefix " + fname );
94+ throw new XPathException (this , ErrorCodes .XPST0081 , "No namespace defined for prefix " + functionName );
6295 }
6396 }
6497
65- public void setArrowFunction (final PathExpr funcSpec , final List <Expression > params ) {
66- this .funcSpec = funcSpec .simplify ();
67- this .parameters = params ;
98+ /**
99+ * Set the XPath/XQuery function to be called by XPath expression.
100+ *
101+ * @param functionSpecExpr the expr that specifies the function to call.
102+ * @param functionParameters the parameters for the function.
103+ */
104+ public void setArrowFunction (final PathExpr functionSpecExpr , final List <Expression > functionParameters ) {
105+ this .functionSpecExpr = functionSpecExpr .simplify ();
106+ this .functionParameters = functionParameters ;
68107 }
69108
70109 @ Override
71110 public void analyze (final AnalyzeContextInfo contextInfo ) throws XPathException {
72- if (getContext ().getXQueryVersion () < 31 ) {
73- throw new XPathException (this ,
74- ErrorCodes .EXXQDY0003 ,
75- "arrow operator is not available before XQuery 3.1" );
111+ if (getContext ().getXQueryVersion () < 31 ) {
112+ throw new XPathException (this , ErrorCodes .EXXQDY0003 , "The Arrow Operator is not available before XQuery 3.1" );
76113 }
77- if (qname != null ) {
78- fcall = NamedFunctionReference .lookupFunction (this , context , qname , parameters .size () + 1 );
114+
115+ if (functionName != null ) {
116+ functionCall = NamedFunctionReference .lookupFunction (this , context , functionName , functionParameters .size () + 1 );
79117 }
118+
80119 this .cachedContextInfo = contextInfo ;
81120 leftExpr .analyze (contextInfo );
82- if (fcall != null ) {
83- fcall .analyze (contextInfo );
121+
122+ if (functionCall != null ) {
123+ functionCall .analyze (contextInfo );
84124 }
85- if (funcSpec != null ) {
86- funcSpec .analyze (contextInfo );
125+
126+ if (functionSpecExpr != null ) {
127+ functionSpecExpr .analyze (contextInfo );
87128 }
88129 }
89130
@@ -94,107 +135,128 @@ public Sequence eval(Sequence contextSequence, final Item contextItem) throws XP
94135 }
95136 contextSequence = leftExpr .eval (contextSequence , null );
96137
97- final FunctionReference fref ;
98- if (fcall != null ) {
99- fref = new FunctionReference (this , fcall );
100- } else {
101- final Sequence funcSeq = funcSpec .eval (contextSequence , contextItem );
102- if (funcSeq .getCardinality () != Cardinality .EXACTLY_ONE )
103- {throw new XPathException (this , ErrorCodes .XPTY0004 ,
104- "Expected exactly one item for the function to be called, got " + funcSeq .getItemCount () +
105- ". Expression: " + ExpressionDumper .dump (funcSpec ));}
106- final Item item0 = funcSeq .itemAt (0 );
107- if (!Type .subTypeOf (item0 .getType (), Type .FUNCTION )) {
108- throw new XPathException (this , ErrorCodes .XPTY0004 ,
109- "Type error: expected function, got " + Type .getTypeName (item0 .getType ()));
110- }
111- fref = (FunctionReference )item0 ;
112- }
138+ @ Nullable FunctionReference functionReference = null ;
113139 try {
114- final List <Expression > fparams = new ArrayList <>(parameters .size () + 1 );
140+ if (functionCall != null ) {
141+ // Prepare call by name
142+ functionReference = new FunctionReference (this , functionCall );
143+
144+ } else {
145+ // Prepare call by expression
146+ final Sequence funcSeq = functionSpecExpr .eval (contextSequence , contextItem );
147+ if (funcSeq .getCardinality () != Cardinality .EXACTLY_ONE ) {
148+ throw new XPathException (this , ErrorCodes .XPTY0004 , "Expected exactly one function item, got " + funcSeq .getItemCount () + ". Expression: " + ExpressionDumper .dump (functionSpecExpr ));
149+ }
150+
151+ final Item item0 = funcSeq .itemAt (0 );
152+ if (!Type .subTypeOf (item0 .getType (), Type .FUNCTION )) {
153+ throw new XPathException (this , ErrorCodes .XPTY0004 , "Type error: expected function, got " + Type .getTypeName (item0 .getType ()));
154+ }
155+
156+ functionReference = (FunctionReference ) item0 ;
157+ }
158+
159+ // Make the call
160+ final List <Expression > fparams = new ArrayList <>(functionParameters .size () + 1 );
115161 fparams .add (new ContextParam (context , contextSequence ));
116- fparams .addAll (parameters );
162+ fparams .addAll (functionParameters );
117163
118- fref .setArguments (fparams );
164+ functionReference .setArguments (fparams );
119165 // need to create a new AnalyzeContextInfo to avoid memory leak
120166 // cachedContextInfo will stay in memory
121- fref .analyze (new AnalyzeContextInfo (cachedContextInfo ));
167+ functionReference .analyze (new AnalyzeContextInfo (cachedContextInfo ));
122168 // Evaluate the function
123- return fref .eval (null );
169+ return functionReference .eval (null );
170+
124171 } finally {
125- fref .close ();
172+ if (functionReference != null ) {
173+ functionReference .close ();
174+ }
126175 }
127176 }
128177
129178 @ Override
130179 public int returnsType () {
131- return fcall == null ? Type .ITEM : fcall .returnsType ();
180+ return functionCall == null ? Type .ITEM : functionCall .returnsType ();
132181 }
133182
134183 @ Override
135184 public Cardinality getCardinality () {
136- return fcall == null ? super .getCardinality () : fcall .getCardinality ();
185+ return functionCall == null ? super .getCardinality () : functionCall .getCardinality ();
137186 }
138187
139188 @ Override
140189 public void dump (final ExpressionDumper dumper ) {
141190 leftExpr .dump (dumper );
142191 dumper .display (" => " );
143- if (fcall != null ) {
144- dumper .display (fcall .getFunction ().getName ()).display ('(' );
192+
193+ if (functionCall != null ) {
194+ dumper .display (functionCall .getFunction ().getName ()).display ('(' );
145195 } else {
146- funcSpec .dump (dumper );
196+ functionSpecExpr .dump (dumper );
147197 }
148- for (int i = 0 ; i < parameters .size (); i ++) {
198+
199+ for (int i = 0 ; i < functionParameters .size (); i ++) {
149200 if (i > 0 ) {
150201 dumper .display (", " );
151- parameters .get (i ).dump (dumper );
202+ functionParameters .get (i ).dump (dumper );
152203 }
153204 }
154205 dumper .display (')' );
155206 }
156207
157208 @ Override
158- public void resetState (boolean postOptimization ) {
209+ public void resetState (final boolean postOptimization ) {
159210 super .resetState (postOptimization );
160211 leftExpr .resetState (postOptimization );
161- if (fcall != null ) {
162- fcall .resetState (postOptimization );
212+
213+ if (functionCall != null ) {
214+ functionCall .resetState (postOptimization );
163215 }
164- if (funcSpec != null ) {
165- funcSpec .resetState (postOptimization );
216+
217+ if (functionSpecExpr != null ) {
218+ functionSpecExpr .resetState (postOptimization );
166219 }
167- for (Expression param : parameters ) {
220+
221+ for (final Expression param : functionParameters ) {
168222 param .resetState (postOptimization );
169223 }
170224 }
171225
172- private class ContextParam extends Function .Placeholder {
226+ static class ContextParam extends Function .Placeholder {
227+ @ Nullable
228+ Sequence sequence ;
173229
174- private Sequence sequence ;
175-
176- ContextParam (XQueryContext context , Sequence sequence ) {
230+ ContextParam (final XQueryContext context , @ Nullable final Sequence sequence ) {
177231 super (context );
178232 this .sequence = sequence ;
179233 }
180234
181235 @ Override
182- public void analyze (AnalyzeContextInfo contextInfo ) throws XPathException {
236+ public void analyze (final AnalyzeContextInfo contextInfo ) throws XPathException {
183237 }
184238
185239 @ Override
186- public Sequence eval (Sequence contextSequence , Item contextItem ) throws XPathException {
240+ public Sequence eval (final Sequence contextSequence , final Item contextItem ) throws XPathException {
187241 return sequence ;
188242 }
189243
190244 @ Override
191245 public int returnsType () {
246+ if (sequence == null ) {
247+ return Type .ITEM ;
248+ }
192249 return sequence .getItemType ();
193250 }
194251
195252 @ Override
196- public void dump (ExpressionDumper dumper ) {
253+ public void dump (final ExpressionDumper dumper ) {
254+ }
197255
256+ @ Override
257+ public void resetState (final boolean postOptimization ) {
258+ super .resetState (postOptimization );
259+ this .sequence = null ;
198260 }
199261 }
200262}
0 commit comments