• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

evolvedbinary / elemental / 982

29 Apr 2025 08:34PM UTC coverage: 56.409% (+0.007%) from 56.402%
982

push

circleci

adamretter
[feature] Improve README.md badges

28451 of 55847 branches covered (50.94%)

Branch coverage included in aggregate %.

77468 of 131924 relevant lines covered (58.72%)

0.59 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

85.06
/exist-core/src/main/java/org/exist/xquery/UserDefinedFunction.java
1
/*
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
 * Use of this software is governed by the Business Source License 1.1
9
 * included in the LICENSE file and at www.mariadb.com/bsl11.
10
 *
11
 * Change Date: 2028-04-27
12
 *
13
 * On the date above, in accordance with the Business Source License, use
14
 * of this software will be governed by the Apache License, Version 2.0.
15
 *
16
 * Additional Use Grant: Production use of the Licensed Work for a permitted
17
 * purpose. A Permitted Purpose is any purpose other than a Competing Use.
18
 * A Competing Use means making the Software available to others in a commercial
19
 * product or service that: substitutes for the Software; substitutes for any
20
 * other product or service we offer using the Software that exists as of the
21
 * date we make the Software available; or offers the same or substantially
22
 * similar functionality as the Software.
23
 *
24
 * NOTE: Parts of this file contain code from 'The eXist-db Authors'.
25
 *       The original license header is included below.
26
 *
27
 * =====================================================================
28
 *
29
 * eXist-db Open Source Native XML Database
30
 * Copyright (C) 2001 The eXist-db Authors
31
 *
32
 * info@exist-db.org
33
 * http://www.exist-db.org
34
 *
35
 * This library is free software; you can redistribute it and/or
36
 * modify it under the terms of the GNU Lesser General Public
37
 * License as published by the Free Software Foundation; either
38
 * version 2.1 of the License, or (at your option) any later version.
39
 *
40
 * This library is distributed in the hope that it will be useful,
41
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
42
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
43
 * Lesser General Public License for more details.
44
 *
45
 * You should have received a copy of the GNU Lesser General Public
46
 * License along with this library; if not, write to the Free Software
47
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
48
 */
49
package org.exist.xquery;
50

51
import org.exist.dom.persistent.DocumentSet;
52
import org.exist.dom.QName;
53
import org.exist.xquery.util.ExpressionDumper;
54
import org.exist.xquery.value.Item;
55
import org.exist.xquery.value.Sequence;
56

57
import java.util.ArrayList;
58
import java.util.List;
59

60
/**
61
 * @author wolf
62
 */
63
public class UserDefinedFunction extends Function implements Cloneable {
64

65
        private Expression body;
66
        
67
        private List<QName> parameters = new ArrayList<>(5);
1✔
68
        
69
        private Sequence[] currentArguments = null;
1✔
70

71
    private DocumentSet[] contextDocs = null;
1✔
72
    
73
    private boolean bodyAnalyzed = false;
1✔
74
    
75
    private FunctionCall call;
76
    
77
    private boolean hasBeenReset = false;
1✔
78

79
    protected boolean visited = false;
1✔
80

81
    private List<ClosureVariable> closureVariables = null;
1✔
82
    
83
        public UserDefinedFunction(XQueryContext context, FunctionSignature signature) {
84
                super(context, signature);
1✔
85
        }
1✔
86
        
87
        public void setFunctionBody(Expression body) {
88
                this.body = body.simplify();
1✔
89
        }
1✔
90

91
    public Expression getFunctionBody() {
92
        return body;
1✔
93
    }
94
    
95
    public void addVariable(final String varName) throws XPathException {
96
                try {
97
                        final QName qname = QName.parse(context, varName, null);
1✔
98
                        addVariable(qname);
1✔
99
                } catch (final QName.IllegalQNameException e) {
1✔
100
                        throw new XPathException(this, ErrorCodes.XPST0081, "No namespace defined for prefix " + varName);
×
101
                }
102
        }
1✔
103
        
104
    public void addVariable(QName varName) throws XPathException {
105
            if (parameters.contains(varName))
1!
106
                        {throw new XPathException(this, "XQST0039: function " + getName() + " is already have parameter with the name "+varName);}
×
107

108
                parameters.add(varName);
1✔
109
    }
1✔
110
    
111
        /* (non-Javadoc)
112
         * @see org.exist.xquery.Function#setArguments(java.util.List)
113
         */
114
        public void setArguments(Sequence[] args, DocumentSet[] contextDocs) throws XPathException {
115
                this.currentArguments = args;
1✔
116
        this.contextDocs = contextDocs;
1✔
117
    }
1✔
118
        
119
        /* (non-Javadoc)
120
         * @see org.exist.xquery.Function#analyze(org.exist.xquery.AnalyzeContextInfo)
121
         */
122
        public void analyze(AnalyzeContextInfo contextInfo) throws XPathException {
123
                hasBeenReset = false;
1✔
124
                
125
                if(call != null && !call.isRecursive()) {
1!
126
                        // Save the local variable stack
127
                        final LocalVariable mark = context.markLocalVariables(true);
1✔
128
                        if (closureVariables != null)
1✔
129
                                // if this is a inline function, context variables are known
130
                        {context.restoreStack(closureVariables);}
1✔
131
                        try {
132
                                LocalVariable var;
133
                                for(final QName varName : parameters) {
1✔
134
                                        var = new LocalVariable(varName);
1✔
135
                                        context.declareVariableBinding(var);
1✔
136
                                }
137

138
                                final AnalyzeContextInfo newContextInfo = new AnalyzeContextInfo(contextInfo);
1✔
139
                                newContextInfo.setParent(this);
1✔
140
                                if (!bodyAnalyzed) {
1✔
141
                                        if(body != null) {
1✔
142
                                                body.analyze(newContextInfo);
1✔
143
                                        }
144
                                        bodyAnalyzed = true;
1✔
145
                                }
146
                        } finally {
1✔
147
                                // restore the local variable stack
148
                                context.popLocalVariables(mark);
1✔
149
                        }
150
                }
151
        }
1✔
152
        
153
        /* (non-Javadoc)
154
         * @see org.exist.xquery.Expression#eval(org.exist.dom.persistent.DocumentSet, org.exist.xquery.value.Sequence, org.exist.xquery.value.Item)
155
         */
156
        public Sequence eval(Sequence contextSequence, Item contextItem) throws XPathException {
157
//        context.expressionStart(this);
158
        context.stackEnter(this);
1✔
159
        // make sure reset state is called after query has finished
160
            hasBeenReset = false;
1✔
161
        // Save the local variable stack
162
        final LocalVariable mark = context.markLocalVariables(true);
1✔
163
        if (closureVariables != null)
1✔
164
                {context.restoreStack(closureVariables);}
1✔
165
        Sequence result = null;
1✔
166
                try {
167
                        QName varName;
168
                        LocalVariable var;
169
                        int j = 0;
1✔
170
                        for (int i = 0; i < parameters.size(); i++, j++) {
1✔
171
                                varName = parameters.get(i);
1✔
172
                                var = new LocalVariable(varName);
1✔
173
                                var.setValue(currentArguments[j]);
1✔
174
                                if (contextDocs != null)
1✔
175
                                        {var.setContextDocs(contextDocs[i]);}
1✔
176
                                context.declareVariableBinding(var);
1✔
177
                                
178
                                Cardinality actualCardinality;
179
                                if (currentArguments[j].isEmpty()) {actualCardinality = Cardinality.EMPTY_SEQUENCE;}
1✔
180
                                else if (currentArguments[j].hasMany()) {actualCardinality = Cardinality._MANY;}
1✔
181
                                else {actualCardinality = Cardinality.EXACTLY_ONE;}
1✔
182
                                
183
                                if (!getSignature().getArgumentTypes()[j].getCardinality().isSuperCardinalityOrEqualOf(actualCardinality))
1✔
184
                                        {throw new XPathException(this, ErrorCodes.XPTY0004, "Invalid cardinality for parameter $" + varName +  
1✔
185
                                                 ". Expected " + getSignature().getArgumentTypes()[j].getCardinality().getHumanDescription() +
1✔
186
                                                 ", got " + currentArguments[j].getItemCount());}
1✔
187
                        }
188
                        result = body.eval(null, null);
1✔
189
                        return result;
1✔
190
                } finally {
191
                        // restore the local variable stack
192
            context.popLocalVariables(mark, result);
1✔
193
            context.stackLeave(this);
1✔
194
//            context.expressionEnd(this);
195
        }
196
        }
197
        
198
        /* (non-Javadoc)
199
     * @see org.exist.xquery.Function#dump(org.exist.xquery.util.ExpressionDumper)
200
     */
201
    public void dump(ExpressionDumper dumper) {
202
        final FunctionSignature signature = getSignature();
×
203
        if (signature.getName() != null)
×
204
                {dumper.display(signature.getName());}
×
205
        dumper.display('(');
×
206
        for(int i = 0; i < signature.getArgumentTypes().length; i++) {
×
207
                        if(i > 0)
×
208
                                {dumper.display(", ");}
×
209
                        dumper.display(signature.getArgumentTypes()[i]);
×
210
                }
211
                dumper.display(") ");
×
212
        dumper.display(signature.getReturnType().toString());
×
213
    }
×
214
    
215
    @Override
216
    public String toString() {
217
        final FunctionSignature signature = getSignature();
1✔
218
        final StringBuilder buf = new StringBuilder();
1✔
219
        if (signature.getName() != null) {
1!
220
                        buf.append(signature.getName());
1✔
221
                }
222
        buf.append('(');
1✔
223
        for (int i = 0; i < signature.getArgumentTypes().length; i++) {
1✔
224
                        if (i > 0) {
1✔
225
                                buf.append(", ");
1✔
226
                        }
227
                        buf.append('$');
1✔
228
                        buf.append(getParameters().get(i));
1✔
229
                        buf.append(" as ");
1✔
230
                        buf.append(signature.getArgumentTypes()[i]);
1✔
231
                }
232
        buf.append(") as ");
1✔
233
                buf.append(signature.getReturnType());
1✔
234
        return buf.toString();
1✔
235
    }
236
    
237
        /* (non-Javadoc)
238
         * @see org.exist.xquery.functions.Function#getDependencies()
239
         */
240
        public int getDependencies() {
241
                return Dependency.CONTEXT_SET + Dependency.CONTEXT_ITEM
×
242
                        + Dependency.CONTEXT_POSITION;
243
        }
244
        
245
        /* (non-Javadoc)
246
         * @see org.exist.xquery.PathExpr#resetState()
247
         */
248
        public void resetState(boolean postOptimization) {
249
                if (hasBeenReset) {
1✔
250
            return;
1✔
251
        }
252
                hasBeenReset = true;
1✔
253
                
254
                super.resetState(postOptimization);
1✔
255
        // Question: understand this test. Why not reset even is not in recursion ?
256
                // Answer: would lead to an infinite loop if the function is recursive.
257
        bodyAnalyzed = false;
1✔
258
        if(body != null) {
1✔
259
                        body.resetState(postOptimization);
1✔
260
                }
261

262
        if (!postOptimization) {
1✔
263
            currentArguments = null;
1✔
264
            contextDocs = null;
1✔
265
        }
266
    }
1✔
267

268
    public void accept(ExpressionVisitor visitor) {
269
        if (visited)
1✔
270
            {return;}
1✔
271
        visited = true;
1✔
272
        visitor.visitUserFunction(this);
1✔
273
    }
1✔
274
    
275
    /**
276
     * Return the functions parameters list
277
     * 
278
     * @return List of function parameters
279
     */
280
    public List<QName> getParameters()
281
    {
282
            return parameters;
1✔
283
    }
284

285
    public synchronized Object clone() {
286
            try {
287
                    final UserDefinedFunction clone = (UserDefinedFunction) super.clone();
1✔
288
                    
289
                    clone.currentArguments = null;
1✔
290
                    clone.contextDocs = null;
1✔
291
                    
292
                    clone.body = this.body; // so body will be analyzed and optimized for all calls of such functions in recursion.  
1✔
293
                
294
                return clone;
1✔
295
            } catch (final CloneNotSupportedException e) {
×
296
                // this shouldn't happen, since we are Cloneable
297
                throw new InternalError();
×
298
            }
299
    }
300
    
301
    public FunctionCall getCaller(){
302
            return call;
×
303
    }
304
    
305
    public void setCaller(FunctionCall call){
306
            this.call = call;
1✔
307
    }
1✔
308
    
309
    public void setClosureVariables(List<ClosureVariable> vars) {
310
            this.closureVariables = vars;
1✔
311
            if (vars != null) {
1✔
312
                    // register the closure with the context so it gets cleared after execution
313
                    context.pushClosure(this);
1✔
314
                }
315
    }
1✔
316

317
    public List<ClosureVariable> getClosureVariables() {
318
        return closureVariables;
1✔
319
    }
320

321
    protected Sequence[] getCurrentArguments() {
322
        return currentArguments;
1✔
323
    }
324
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2025 Coveralls, Inc