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.List;
9   import java.util.function.Supplier;
10  import java.util.stream.Collectors;
11  
12  import dev.metaschema.core.metapath.DynamicContext;
13  import dev.metaschema.core.metapath.IExpression;
14  import dev.metaschema.core.metapath.StaticMetapathException;
15  import dev.metaschema.core.metapath.function.IFunction;
16  import dev.metaschema.core.metapath.item.IItem;
17  import dev.metaschema.core.metapath.item.ISequence;
18  import dev.metaschema.core.util.ObjectUtils;
19  import edu.umd.cs.findbugs.annotations.NonNull;
20  import nl.talsmasoftware.lazy4j.Lazy;
21  
22  /**
23   * Executes a function call based on the provided function and multiple argument
24   * expressions that are used to determine the function arguments.
25   * <p>
26   * This class handles static function calls where the name of the function is
27   * known during static analysis (the parsing phase), as opposed to dynamic or
28   * anonymous function calls where the name is not available or known until
29   * execution.
30   * <p>
31   * Static functions are resolved during the parsing phase and must exist in the
32   * function registry.
33   */
34  public class StaticFunctionCall
35      extends AbstractExpression {
36    @NonNull
37    private final Lazy<IFunction> functionSupplier;
38    @NonNull
39    private final List<IExpression> arguments;
40  
41    /**
42     * Construct a new function call expression.
43     *
44     * @param text
45     *          the parsed text of the expression
46     * @param functionSupplier
47     *          the function supplier, which is used to lazy fetch the function
48     *          allowing the containing Metapaths to parse even if a function does
49     *          not exist during the parsing phase.
50     * @param arguments
51     *          the expressions used to provide arguments to the function call
52     */
53    public StaticFunctionCall(
54        @NonNull String text,
55        @NonNull Supplier<IFunction> functionSupplier,
56        @NonNull List<IExpression> arguments) {
57      super(text);
58      this.functionSupplier = ObjectUtils.notNull(Lazy.of(functionSupplier));
59      this.arguments = arguments;
60    }
61  
62    /**
63     * Retrieve the associated function.
64     *
65     * @return the function or {@code null} if no function matched the defined name
66     *         and arguments
67     * @throws StaticMetapathException
68     *           if the function was not found
69     */
70    @NonNull
71    public IFunction getFunction() {
72      IFunction function = functionSupplier.get();
73      if (function == null) {
74        throw new StaticMetapathException(
75            StaticMetapathException.NO_FUNCTION_MATCH,
76            String.format(
77                "No matching function found for the given name and arguments"));
78      }
79      return function;
80    }
81  
82    @Override
83    public List<IExpression> getChildren() {
84      return arguments;
85    }
86  
87    @Override
88    public Class<? extends IItem> getBaseResultType() {
89      return getFunction().getResult().getType().getItemClass();
90    }
91  
92    @SuppressWarnings("null")
93    @Override
94    public String toCSTString() {
95      return String.format("%s[name=%s, arity=%d]", getClass().getName(), getFunction().getQName(),
96          getFunction().arity());
97    }
98  
99    @Override
100   public <RESULT, CONTEXT> RESULT accept(IExpressionVisitor<RESULT, CONTEXT> visitor, CONTEXT context) {
101     return visitor.visitStaticFunctionCall(this, context);
102   }
103 
104   @Override
105   protected ISequence<?> evaluate(DynamicContext dynamicContext, ISequence<?> focus) {
106     List<ISequence<?>> arguments = ObjectUtils.notNull(this.arguments.stream()
107         .map(expression -> expression.accept(dynamicContext, focus).contentsAsSequence())
108         .collect(Collectors.toList()));
109 
110     IFunction function = getFunction();
111     return function.execute(arguments, dynamicContext, focus);
112   }
113 }