|
35 | 35 | */
|
36 | 36 | public class UserDefinedFunction extends Function implements Cloneable {
|
37 | 37 |
|
38 |
| - private Expression body; |
39 |
| - |
40 |
| - private List<QName> parameters = new ArrayList<>(5); |
41 |
| - |
42 |
| - private Sequence[] currentArguments = null; |
43 |
| - |
| 38 | + private final List<QName> parameters = new ArrayList<>(5); |
| 39 | + protected boolean visited = false; |
| 40 | + private Expression body; |
| 41 | + private Sequence[] currentArguments = null; |
44 | 42 | private DocumentSet[] contextDocs = null;
|
45 |
| - |
46 | 43 | private boolean bodyAnalyzed = false;
|
47 |
| - |
48 | 44 | private FunctionCall call;
|
49 |
| - |
50 | 45 | private boolean hasBeenReset = false;
|
51 |
| - |
52 |
| - protected boolean visited = false; |
53 |
| - |
54 | 46 | private List<ClosureVariable> closureVariables = null;
|
55 |
| - |
56 |
| - public UserDefinedFunction(XQueryContext context, FunctionSignature signature) { |
57 |
| - super(context, signature); |
58 |
| - } |
59 |
| - |
60 |
| - public void setFunctionBody(Expression body) { |
61 |
| - this.body = body.simplify(); |
62 |
| - } |
| 47 | + |
| 48 | + public UserDefinedFunction(XQueryContext context, FunctionSignature signature) { |
| 49 | + super(context, signature); |
| 50 | + } |
63 | 51 |
|
64 | 52 | public Expression getFunctionBody() {
|
65 | 53 | return body;
|
66 | 54 | }
|
67 |
| - |
| 55 | + |
| 56 | + public void setFunctionBody(Expression body) { |
| 57 | + this.body = body.simplify(); |
| 58 | + } |
| 59 | + |
68 | 60 | public void addVariable(final String varName) throws XPathException {
|
69 |
| - try { |
70 |
| - final QName qname = QName.parse(context, varName, null); |
71 |
| - addVariable(qname); |
72 |
| - } catch (final QName.IllegalQNameException e) { |
73 |
| - throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + varName); |
74 |
| - } |
75 |
| - } |
76 |
| - |
| 61 | + try { |
| 62 | + final QName qname = QName.parse(context, varName, null); |
| 63 | + addVariable(qname); |
| 64 | + } catch (final QName.IllegalQNameException e) { |
| 65 | + throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + varName); |
| 66 | + } |
| 67 | + } |
| 68 | + |
77 | 69 | public void addVariable(QName varName) throws XPathException {
|
78 |
| - if (parameters.contains(varName)) |
79 |
| - {throw new XPathException(this, "XQST0039: function " + getName() + " is already have parameter with the name "+varName);} |
| 70 | + if (parameters.contains(varName)) { |
| 71 | + throw new XPathException(this, ErrorCodes.XQST0039, "function " + getName() + " already has a parameter with the name " + varName); |
| 72 | + } |
80 | 73 |
|
81 |
| - parameters.add(varName); |
| 74 | + parameters.add(varName); |
82 | 75 | }
|
83 |
| - |
84 |
| - /* (non-Javadoc) |
85 |
| - * @see org.exist.xquery.Function#setArguments(java.util.List) |
86 |
| - */ |
87 |
| - public void setArguments(Sequence[] args, DocumentSet[] contextDocs) throws XPathException { |
88 |
| - this.currentArguments = args; |
| 76 | + |
| 77 | + public void setArguments(Sequence[] args, DocumentSet[] contextDocs) throws XPathException { |
| 78 | + this.currentArguments = args; |
89 | 79 | this.contextDocs = contextDocs;
|
90 | 80 | }
|
91 |
| - |
92 |
| - /* (non-Javadoc) |
93 |
| - * @see org.exist.xquery.Function#analyze(org.exist.xquery.AnalyzeContextInfo) |
94 |
| - */ |
95 |
| - public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { |
96 |
| - hasBeenReset = false; |
97 |
| - |
98 |
| - if(call != null && !call.isRecursive()) { |
99 |
| - // Save the local variable stack |
100 |
| - final LocalVariable mark = context.markLocalVariables(true); |
101 |
| - if (closureVariables != null) |
102 |
| - // if this is a inline function, context variables are known |
103 |
| - {context.restoreStack(closureVariables);} |
104 |
| - try { |
105 |
| - LocalVariable var; |
106 |
| - for(final QName varName : parameters) { |
107 |
| - var = new LocalVariable(varName); |
108 |
| - context.declareVariableBinding(var); |
109 |
| - } |
110 |
| - |
111 |
| - final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo); |
112 |
| - newContextInfo.setParent(this); |
113 |
| - if (!bodyAnalyzed) { |
114 |
| - if(body != null) { |
115 |
| - body.analyze(newContextInfo); |
116 |
| - } |
117 |
| - bodyAnalyzed = true; |
118 |
| - } |
119 |
| - } finally { |
120 |
| - // restore the local variable stack |
121 |
| - context.popLocalVariables(mark); |
122 |
| - } |
123 |
| - } |
124 |
| - } |
125 |
| - |
126 |
| - /* (non-Javadoc) |
127 |
| - * @see org.exist.xquery.Expression#eval(org.exist.dom.persistent.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item) |
128 |
| - */ |
129 |
| - public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { |
| 81 | + |
| 82 | + @Override |
| 83 | + public void analyze(AnalyzeContextInfo contextInfo) throws XPathException { |
| 84 | + hasBeenReset = false; |
| 85 | + |
| 86 | + if (call != null && !call.isRecursive()) { |
| 87 | + // Save the local variable stack |
| 88 | + final LocalVariable mark = context.markLocalVariables(true); |
| 89 | + if (closureVariables != null) { |
| 90 | + // if this is a inline function, context variables are known |
| 91 | + context.restoreStack(closureVariables); |
| 92 | + } |
| 93 | + try { |
| 94 | + LocalVariable var; |
| 95 | + for (final QName varName : parameters) { |
| 96 | + var = new LocalVariable(varName); |
| 97 | + context.declareVariableBinding(var); |
| 98 | + } |
| 99 | + |
| 100 | + final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo); |
| 101 | + newContextInfo.setParent(this); |
| 102 | + if (!bodyAnalyzed) { |
| 103 | + if (body != null) { |
| 104 | + body.analyze(newContextInfo); |
| 105 | + } |
| 106 | + bodyAnalyzed = true; |
| 107 | + } |
| 108 | + } finally { |
| 109 | + // restore the local variable stack |
| 110 | + context.popLocalVariables(mark); |
| 111 | + } |
| 112 | + } |
| 113 | + } |
| 114 | + |
| 115 | + @Override |
| 116 | + public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException { |
130 | 117 | // context.expressionStart(this);
|
131 | 118 | context.stackEnter(this);
|
132 | 119 | // make sure reset state is called after query has finished
|
133 |
| - hasBeenReset = false; |
| 120 | + hasBeenReset = false; |
134 | 121 | // Save the local variable stack
|
135 | 122 | final LocalVariable mark = context.markLocalVariables(true);
|
136 |
| - if (closureVariables != null) |
137 |
| - {context.restoreStack(closureVariables);} |
| 123 | + if (closureVariables != null) { |
| 124 | + context.restoreStack(closureVariables); |
| 125 | + } |
138 | 126 | Sequence result = null;
|
139 |
| - try { |
140 |
| - QName varName; |
141 |
| - LocalVariable var; |
142 |
| - int j = 0; |
143 |
| - for (int i = 0; i < parameters.size(); i++, j++) { |
144 |
| - varName = parameters.get(i); |
145 |
| - var = new LocalVariable(varName); |
146 |
| - var.setValue(currentArguments[j]); |
147 |
| - if (contextDocs != null) |
148 |
| - {var.setContextDocs(contextDocs[i]);} |
149 |
| - context.declareVariableBinding(var); |
150 |
| - |
151 |
| - Cardinality actualCardinality; |
152 |
| - if (currentArguments[j].isEmpty()) {actualCardinality = Cardinality.EMPTY_SEQUENCE;} |
153 |
| - else if (currentArguments[j].hasMany()) {actualCardinality = Cardinality._MANY;} |
154 |
| - else {actualCardinality = Cardinality.EXACTLY_ONE;} |
155 |
| - |
156 |
| - if (!getSignature().getArgumentTypes()[j].getCardinality().isSuperCardinalityOrEqualOf(actualCardinality)) |
157 |
| - {throw new XPathException(this, ErrorCodes.XPTY0004, "Invalid cardinality for parameter $" + varName + |
158 |
| - ". Expected " + getSignature().getArgumentTypes()[j].getCardinality().getHumanDescription() + |
159 |
| - ", got " + currentArguments[j].getItemCount());} |
160 |
| - } |
161 |
| - result = body.eval(null, null); |
162 |
| - return result; |
163 |
| - } finally { |
164 |
| - // restore the local variable stack |
| 127 | + try { |
| 128 | + QName varName; |
| 129 | + LocalVariable var; |
| 130 | + int j = 0; |
| 131 | + for (int i = 0; i < parameters.size(); i++, j++) { |
| 132 | + varName = parameters.get(i); |
| 133 | + var = new LocalVariable(varName); |
| 134 | + var.setValue(currentArguments[j]); |
| 135 | + if (contextDocs != null) { |
| 136 | + var.setContextDocs(contextDocs[i]); |
| 137 | + } |
| 138 | + context.declareVariableBinding(var); |
| 139 | + |
| 140 | + Cardinality actualCardinality; |
| 141 | + if (currentArguments[j].isEmpty()) { |
| 142 | + actualCardinality = Cardinality.EMPTY_SEQUENCE; |
| 143 | + } else if (currentArguments[j].hasMany()) { |
| 144 | + actualCardinality = Cardinality._MANY; |
| 145 | + } else { |
| 146 | + actualCardinality = Cardinality.EXACTLY_ONE; |
| 147 | + } |
| 148 | + |
| 149 | + if (!getSignature().getArgumentTypes()[j].getCardinality().isSuperCardinalityOrEqualOf(actualCardinality)) { |
| 150 | + throw new XPathException(this, ErrorCodes.XPTY0004, "Invalid cardinality for parameter $" + varName + |
| 151 | + ". Expected " + getSignature().getArgumentTypes()[j].getCardinality().getHumanDescription() + |
| 152 | + ", got " + currentArguments[j].getItemCount()); |
| 153 | + } |
| 154 | + } |
| 155 | + result = body.eval(null, null); |
| 156 | + return result; |
| 157 | + } finally { |
| 158 | + // restore the local variable stack |
165 | 159 | context.popLocalVariables(mark, result);
|
166 | 160 | context.stackLeave(this);
|
167 | 161 | // context.expressionEnd(this);
|
@@ -209,91 +203,89 @@ public String toString() {
|
209 | 203 | buf.append(signature.getReturnType());
|
210 | 204 | return buf.toString();
|
211 | 205 | }
|
212 |
| - |
213 |
| - /* (non-Javadoc) |
214 |
| - * @see org.exist.xquery.functions.Function#getDependencies() |
215 |
| - */ |
216 |
| - public int getDependencies() { |
217 |
| - return Dependency.CONTEXT_SET + Dependency.CONTEXT_ITEM |
218 |
| - + Dependency.CONTEXT_POSITION; |
219 |
| - } |
220 |
| - |
221 |
| - /* (non-Javadoc) |
222 |
| - * @see org.exist.xquery.PathExpr#resetState() |
223 |
| - */ |
224 |
| - public void resetState(boolean postOptimization) { |
225 |
| - if (hasBeenReset) { |
| 206 | + |
| 207 | + @Override |
| 208 | + public int getDependencies() { |
| 209 | + return Dependency.CONTEXT_SET + Dependency.CONTEXT_ITEM |
| 210 | + + Dependency.CONTEXT_POSITION; |
| 211 | + } |
| 212 | + |
| 213 | + @Override |
| 214 | + public void resetState(boolean postOptimization) { |
| 215 | + if (hasBeenReset) { |
226 | 216 | return;
|
227 | 217 | }
|
228 |
| - hasBeenReset = true; |
229 |
| - |
230 |
| - super.resetState(postOptimization); |
| 218 | + hasBeenReset = true; |
| 219 | + |
| 220 | + super.resetState(postOptimization); |
231 | 221 | // Question: understand this test. Why not reset even is not in recursion ?
|
232 |
| - // Answer: would lead to an infinite loop if the function is recursive. |
| 222 | + // Answer: would lead to an infinite loop if the function is recursive. |
233 | 223 | bodyAnalyzed = false;
|
234 |
| - if(body != null) { |
235 |
| - body.resetState(postOptimization); |
236 |
| - } |
| 224 | + if (body != null) { |
| 225 | + body.resetState(postOptimization); |
| 226 | + } |
237 | 227 |
|
238 | 228 | if (!postOptimization) {
|
239 | 229 | currentArguments = null;
|
240 | 230 | contextDocs = null;
|
241 | 231 | }
|
242 | 232 | }
|
243 | 233 |
|
| 234 | + @Override |
244 | 235 | public void accept(ExpressionVisitor visitor) {
|
245 |
| - if (visited) |
246 |
| - {return;} |
| 236 | + if (visited) { |
| 237 | + return; |
| 238 | + } |
247 | 239 | visited = true;
|
248 | 240 | visitor.visitUserFunction(this);
|
249 | 241 | }
|
250 |
| - |
| 242 | + |
251 | 243 | /**
|
252 | 244 | * Return the functions parameters list
|
253 |
| - * |
| 245 | + * |
254 | 246 | * @return List of function parameters
|
255 | 247 | */
|
256 |
| - public List<QName> getParameters() |
257 |
| - { |
258 |
| - return parameters; |
| 248 | + public List<QName> getParameters() { |
| 249 | + return parameters; |
259 | 250 | }
|
260 | 251 |
|
| 252 | + @Override |
261 | 253 | public synchronized Object clone() {
|
262 |
| - try { |
263 |
| - final UserDefinedFunction clone = (UserDefinedFunction) super.clone(); |
264 |
| - |
265 |
| - clone.currentArguments = null; |
266 |
| - clone.contextDocs = null; |
267 |
| - |
268 |
| - clone.body = this.body; // so body will be analyzed and optimized for all calls of such functions in recursion. |
269 |
| - |
270 |
| - return clone; |
271 |
| - } catch (final CloneNotSupportedException e) { |
272 |
| - // this shouldn't happen, since we are Cloneable |
273 |
| - throw new InternalError(); |
274 |
| - } |
275 |
| - } |
276 |
| - |
277 |
| - public FunctionCall getCaller(){ |
278 |
| - return call; |
| 254 | + try { |
| 255 | + final UserDefinedFunction clone = (UserDefinedFunction) super.clone(); |
| 256 | + |
| 257 | + clone.currentArguments = null; |
| 258 | + clone.contextDocs = null; |
| 259 | + |
| 260 | + clone.body = this.body; // so body will be analyzed and optimized for all calls of such functions in recursion. |
| 261 | + |
| 262 | + return clone; |
| 263 | + } catch (final CloneNotSupportedException e) { |
| 264 | + // this shouldn't happen, since we are Cloneable |
| 265 | + throw new InternalError(); |
| 266 | + } |
279 | 267 | }
|
280 |
| - |
281 |
| - public void setCaller(FunctionCall call){ |
282 |
| - this.call = call; |
| 268 | + |
| 269 | + public FunctionCall getCaller() { |
| 270 | + return call; |
283 | 271 | }
|
284 |
| - |
285 |
| - public void setClosureVariables(List<ClosureVariable> vars) { |
286 |
| - this.closureVariables = vars; |
287 |
| - if (vars != null) { |
288 |
| - // register the closure with the context so it gets cleared after execution |
289 |
| - context.pushClosure(this); |
290 |
| - } |
| 272 | + |
| 273 | + public void setCaller(FunctionCall call) { |
| 274 | + this.call = call; |
291 | 275 | }
|
292 | 276 |
|
293 | 277 | public List<ClosureVariable> getClosureVariables() {
|
294 | 278 | return closureVariables;
|
295 | 279 | }
|
296 | 280 |
|
| 281 | + public void setClosureVariables(List<ClosureVariable> vars) { |
| 282 | + this.closureVariables = vars; |
| 283 | + if (vars != null) { |
| 284 | + // register the closure with the context so it gets cleared after execution |
| 285 | + context.pushClosure(this); |
| 286 | + } |
| 287 | + } |
| 288 | + |
297 | 289 | protected Sequence[] getCurrentArguments() {
|
298 | 290 | return currentArguments;
|
299 | 291 | }
|
|
0 commit comments