1   /*
2    * SPDX-FileCopyrightText: none
3    * SPDX-License-Identifier: CC0-1.0
4    */
5   
6   package dev.metaschema.core.metapath.cst;
7   
8   import java.util.EnumSet;
9   import java.util.Iterator;
10  import java.util.List;
11  import java.util.Set;
12  import java.util.UUID;
13  
14  import dev.metaschema.core.metapath.DynamicContext;
15  import dev.metaschema.core.metapath.IExpression;
16  import dev.metaschema.core.metapath.function.IArgument;
17  import dev.metaschema.core.metapath.function.IFunction;
18  import dev.metaschema.core.metapath.function.impl.AbstractFunction;
19  import dev.metaschema.core.metapath.item.IItem;
20  import dev.metaschema.core.metapath.item.ISequence;
21  import dev.metaschema.core.metapath.type.ISequenceType;
22  import dev.metaschema.core.util.ObjectUtils;
23  import edu.umd.cs.findbugs.annotations.NonNull;
24  import edu.umd.cs.findbugs.annotations.Nullable;
25  
26  /**
27   * Executes an unnamed function call based on a client provided Metapath
28   * expression that is declared inline within a Metapath expression.
29   */
30  public class AnonymousFunctionCall
31      extends AbstractExpression {
32    private final AnonymousFunction function;
33  
34    /**
35     * Construct a new function call expression.
36     *
37     * @param text
38     *          the parsed text of the expression
39     * @param arguments
40     *          the parameter declarations for the function call
41     * @param result
42     *          the expected result of the function call
43     * @param body
44     *          the Metapath expression that implements the logic of the function
45     */
46    public AnonymousFunctionCall(
47        @NonNull String text,
48        @NonNull List<IArgument> arguments,
49        @NonNull ISequenceType result,
50        @NonNull IExpression body) {
51      super(text);
52      this.function = new AnonymousFunction(arguments, result, body);
53    }
54  
55    /**
56     * Get the function associated with this Metapath expression.
57     *
58     * @return the function
59     */
60    protected AnonymousFunction getFunction() {
61      return function;
62    }
63  
64    @Override
65    public List<IExpression> getChildren() {
66      return ObjectUtils.notNull(List.of(getFunction().getBody()));
67    }
68  
69    @Override
70    public Class<? extends IItem> getBaseResultType() {
71      return IFunction.class;
72    }
73  
74    @Override
75    public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
76      return visitor.visitAnonymousFunctionCall(this, context);
77    }
78  
79    @Override
80    protected ISequence<?> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
81      return ISequence.of(getFunction());
82    }
83  
84    @SuppressWarnings("null")
85    @Override
86    public String toCSTString() {
87      IFunction function = getFunction();
88      return String.format("%s[arguments=%s,return=%s]",
89          getClass().getName(),
90          function.getArguments(),
91          function.getResult().toSignature());
92    }
93  
94    private static final class AnonymousFunction
95        extends AbstractFunction {
96      @NonNull
97      private static final Set<FunctionProperty> PROPERTIES = ObjectUtils.notNull(EnumSet.of(
98          FunctionProperty.DETERMINISTIC));
99      @NonNull
100     private final ISequenceType result;
101     @NonNull
102     private final IExpression body;
103 
104     public AnonymousFunction(
105         @NonNull List<IArgument> arguments,
106         @NonNull ISequenceType result,
107         @NonNull IExpression body) {
108       super("(anonymous)-" + UUID.randomUUID().toString(), "", arguments);
109       this.result = result;
110       this.body = body;
111     }
112 
113     @Override
114     public ISequenceType getResult() {
115       return result;
116     }
117 
118     @NonNull
119     protected IExpression getBody() {
120       return body;
121     }
122 
123     @Override
124     public Set<FunctionProperty> getProperties() {
125       return PROPERTIES;
126     }
127 
128     @Override
129     public boolean isNamedFunction() {
130       // these functions are anonymous
131       return false;
132     }
133 
134     @Override
135     @NonNull
136     protected ISequence<?> executeInternal(
137         @NonNull List<ISequence<?>> arguments,
138         @NonNull DynamicContext dynamicContext,
139         @Nullable IItem focus) {
140 
141       DynamicContext subContext = dynamicContext.subContext();
142       if (arguments.size() != getArguments().size()) {
143         throw new IllegalArgumentException("Number of arguments does not match the number of parameters.");
144       }
145 
146       Iterator<? extends ISequence<?>> args = arguments.iterator();
147       Iterator<IArgument> params = getArguments().iterator();
148       while (args.hasNext() && params.hasNext()) {
149         ISequence<?> sequence = args.next();
150         IArgument param = params.next();
151 
152         subContext.bindVariableValue(param.getName(), ObjectUtils.notNull(sequence));
153       }
154 
155       // the focus is not present according to
156       // https://www.w3.org/TR/xpath-31/#id-eval-function-call
157       // paragraph 5.b.ii
158       return body.accept(subContext, ISequence.empty());
159     }
160   }
161 }